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.7) 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 time 20 21from autotest_lib.client.bin import utils 22from autotest_lib.client.common_lib import error 23from autotest_lib.client.cros import power_status 24from numpy import uint32 25 26 27# TODO(tbroch): dram domain only for server class CPU's 28VALID_DOMAINS = ['pkg', 'pp0', 'pp1'] 29 30 31def create_rapl(domains=None): 32 """Create a set of Rapl instances. 33 34 Args: 35 domains: list of strings, representing desired RAPL domains to 36 instantiate. 37 38 Returns: 39 list of Rapl objects. 40 41 Raises: 42 error.TestFail: If domain is invalid. 43 """ 44 if not domains: 45 domains = VALID_DOMAINS 46 rapl_list = [] 47 for domain in set(domains): 48 rapl_list.append(Rapl(domain)) 49 return rapl_list 50 51 52class Rapl(power_status.PowerMeasurement): 53 """Class to expose RAPL functionality. 54 55 Public attibutes: 56 domain: string, name of power rail domain. 57 58 Private attributes: 59 _joules_per_lsb: float, joules per lsb of energy. 60 _joules_start: float, joules measured at the beginning of operation. 61 _time_start: float, time in seconds since Epoch. 62 63 Public methods: 64 refresh(): Refreshes starting point of RAPL power measurement and 65 returns power in watts. 66 """ 67 _DOMAIN_MSRS = {'pkg': {'power_limit': 0x610, 68 'energy_status': 0x611, 69 'perf_status': 0x613, 70 'power_info': 0x614}, 71 'pp0': {'power_limit': 0x638, 72 'energy_status': 0x639, 73 'policy': 0x63a, 74 'perf_status': 0x63b}, 75 'pp1': {'power_limit': 0x640, 76 'energy_status': 0x641, 77 'policy': 0x642}, 78 'dram': {'power_limit': 0x618, 79 'energy_status': 0x619, 80 'perf_status': 0x61b, 81 'power_info': 0x61c}} 82 83 # Units for Power, Energy & Time 84 _POWER_UNIT_MSR = 0x606 85 86 _POWER_UNIT_OFFSET = 0x0 87 _POWER_UNIT_MASK = 0x0F 88 _ENERGY_UNIT_OFFSET = 0x08 89 _ENERGY_UNIT_MASK = 0x1F00 90 _TIME_UNIT_OFFSET = 0x10 91 _TIME_UNIT_MASK = 0xF000 92 93 # Maximum number of seconds allowable between energy status samples. See 94 # docstring in power method for complete details. 95 _MAX_MEAS_SECS = 1800 96 97 98 def __init__(self, domain): 99 """Constructor for Rapl class. 100 101 Args: 102 domain: string, name of power rail domain 103 104 Raises: 105 error.TestError: If domain is invalid 106 """ 107 if domain not in VALID_DOMAINS: 108 raise error.TestError("domain %s not in valid domains ( %s )" % 109 (domain, ", ".join(VALID_DOMAINS))) 110 super(Rapl, self).__init__(domain) 111 112 self._joules_per_lsb = self._get_joules_per_lsb() 113 logging.debug("RAPL %s joules_per_lsb = %.3e", domain, 114 self._joules_per_lsb) 115 self._joules_start = self._get_energy() 116 self._time_start = time.time() 117 118 119 def __del__(self): 120 """Deconstructor for Rapl class. 121 122 Raises: 123 error.TestError: If the joules per lsb changed during sampling time. 124 """ 125 if self._get_joules_per_lsb() != self._joules_per_lsb: 126 raise error.TestError("Results suspect as joules_per_lsb changed " 127 "during sampling") 128 129 130 def _rdmsr(self, msr, cpu_id=0): 131 """Read MSR ( Model Specific Register ) 132 133 Read MSR value for x86 systems. 134 135 Args: 136 msr: Integer, address of MSR. 137 cpu_id: Integer, number of CPU to read MSR for. Default 0. 138 Returns: 139 Integer, representing the requested MSR register. 140 """ 141 return int(utils.system_output('iotools rdmsr %d %d' % 142 (cpu_id, msr)), 0) 143 144 145 def _get_joules_per_lsb(self): 146 """Calculate and return energy in joules per lsb. 147 148 Value used as a multiplier while reading the RAPL energy status MSR. 149 150 Returns: 151 Float, value of joules per lsb. 152 """ 153 msr_val = self._rdmsr(self._POWER_UNIT_MSR) 154 return 1.0 / pow(2, (msr_val & self._ENERGY_UNIT_MASK) >> 155 self._ENERGY_UNIT_OFFSET) 156 157 158 def _get_energy(self): 159 """Get energy reading. 160 161 Returns: 162 Integer (32-bit), representing total energy consumed since power-on. 163 """ 164 msr = self._DOMAIN_MSRS[self.domain]['energy_status'] 165 return uint32(self._rdmsr(msr)) 166 167 168 def domain(self): 169 """Convenience method to expose Rapl instance domain name. 170 171 Returns: 172 string, name of Rapl domain. 173 """ 174 return self.domain 175 176 177 def refresh(self): 178 """Calculate the average power used for RAPL domain. 179 180 Note, Intel doc says ~60secs but in practice it seems much longer on 181 laptop class devices. Using numpy's uint32 correctly calculates single 182 wraparound. Risk is whether wraparound occurs multiple times. As the 183 RAPL facilities don't provide any way to identify multiple wraparounds 184 it does present a risk to long samples. To remedy, method raises an 185 exception for long measurements that should be well below the multiple 186 wraparound window. Length of time between measurements must be managed 187 by periodic logger instantiating this object to avoid the exception. 188 189 Returns: 190 float, average power (in watts) over the last time interval tracked. 191 Raises: 192 error.TestError: If time between measurements too great. 193 """ 194 joules_now = self._get_energy() 195 time_now = time.time() 196 energy_used = (joules_now - self._joules_start) * self._joules_per_lsb 197 time_used = time_now - self._time_start 198 if time_used > self._MAX_MEAS_SECS: 199 raise error.TestError("Time between reads of %s energy status " 200 "register was > %d seconds" % \ 201 (self.domain, self._MAX_MEAS_SECS)) 202 average_power = energy_used / time_used 203 self._joules_start = joules_now 204 self._time_start = time_now 205 return average_power 206