• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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