xref: /aosp_15_r20/external/autotest/client/cros/power/power_rapl.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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