• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2013 The Chromium 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
5import atexit
6import logging
7import re
8
9from devil.android import device_errors
10
11logger = logging.getLogger(__name__)
12
13
14class PerfControl(object):
15  """Provides methods for setting the performance mode of a device."""
16
17  _AVAILABLE_GOVERNORS_REL_PATH = 'cpufreq/scaling_available_governors'
18  _CPU_FILE_PATTERN = re.compile(r'^cpu\d+$')
19  _CPU_PATH = '/sys/devices/system/cpu'
20  _KERNEL_MAX = '/sys/devices/system/cpu/kernel_max'
21
22  def __init__(self, device):
23    self._device = device
24    self._cpu_files = [
25        filename
26        for filename in self._device.ListDirectory(self._CPU_PATH, as_root=True)
27        if self._CPU_FILE_PATTERN.match(filename)]
28    assert self._cpu_files, 'Failed to detect CPUs.'
29    self._cpu_file_list = ' '.join(self._cpu_files)
30    logger.info('CPUs found: %s', self._cpu_file_list)
31
32    self._have_mpdecision = self._device.FileExists('/system/bin/mpdecision')
33
34    raw = self._ReadEachCpuFile(self._AVAILABLE_GOVERNORS_REL_PATH)
35    self._available_governors = [
36        (cpu, raw_governors.strip().split() if not exit_code else None)
37        for cpu, raw_governors, exit_code in raw]
38
39  def SetHighPerfMode(self):
40    """Sets the highest stable performance mode for the device."""
41    try:
42      self._device.EnableRoot()
43    except device_errors.CommandFailedError:
44      message = 'Need root for performance mode. Results may be NOISY!!'
45      logger.warning(message)
46      # Add an additional warning at exit, such that it's clear that any results
47      # may be different/noisy (due to the lack of intended performance mode).
48      atexit.register(logger.warning, message)
49      return
50
51    product_model = self._device.product_model
52    # TODO(epenner): Enable on all devices (http://crbug.com/383566)
53    if 'Nexus 4' == product_model:
54      self._ForceAllCpusOnline(True)
55      if not self._AllCpusAreOnline():
56        logger.warning('Failed to force CPUs online. Results may be NOISY!')
57      self.SetScalingGovernor('performance')
58    elif 'Nexus 5' == product_model:
59      self._ForceAllCpusOnline(True)
60      if not self._AllCpusAreOnline():
61        logger.warning('Failed to force CPUs online. Results may be NOISY!')
62      self.SetScalingGovernor('performance')
63      self._SetScalingMaxFreq(1190400)
64      self._SetMaxGpuClock(200000000)
65    else:
66      self.SetScalingGovernor('performance')
67
68  def SetPerfProfilingMode(self):
69    """Enables all cores for reliable perf profiling."""
70    self._ForceAllCpusOnline(True)
71    self.SetScalingGovernor('performance')
72    if not self._AllCpusAreOnline():
73      if not self._device.HasRoot():
74        raise RuntimeError('Need root to force CPUs online.')
75      raise RuntimeError('Failed to force CPUs online.')
76
77  def SetDefaultPerfMode(self):
78    """Sets the performance mode for the device to its default mode."""
79    if not self._device.HasRoot():
80      return
81    product_model = self._device.product_model
82    if 'Nexus 5' == product_model:
83      if self._AllCpusAreOnline():
84        self._SetScalingMaxFreq(2265600)
85        self._SetMaxGpuClock(450000000)
86
87    governor_mode = {
88        'GT-I9300': 'pegasusq',
89        'Galaxy Nexus': 'interactive',
90        'Nexus 4': 'ondemand',
91        'Nexus 5': 'ondemand',
92        'Nexus 7': 'interactive',
93        'Nexus 10': 'interactive'
94    }.get(product_model, 'ondemand')
95    self.SetScalingGovernor(governor_mode)
96    self._ForceAllCpusOnline(False)
97
98  def GetCpuInfo(self):
99    online = (output.rstrip() == '1' and status == 0
100              for (_, output, status) in self._ForEachCpu('cat "$CPU/online"'))
101    governor = (output.rstrip() if status == 0 else None
102                for (_, output, status)
103                in self._ForEachCpu('cat "$CPU/cpufreq/scaling_governor"'))
104    return zip(self._cpu_files, online, governor)
105
106  def _ForEachCpu(self, cmd):
107    script = '; '.join([
108        'for CPU in %s' % self._cpu_file_list,
109        'do %s' % cmd,
110        'echo -n "%~%$?%~%"',
111        'done'
112    ])
113    output = self._device.RunShellCommand(
114        script, cwd=self._CPU_PATH, check_return=True, as_root=True, shell=True)
115    output = '\n'.join(output).split('%~%')
116    return zip(self._cpu_files, output[0::2], (int(c) for c in output[1::2]))
117
118  def _WriteEachCpuFile(self, path, value):
119    self._ConditionallyWriteEachCpuFile(path, value, condition='true')
120
121  def _ConditionallyWriteEachCpuFile(self, path, value, condition):
122    template = (
123        '{condition} && test -e "$CPU/{path}" && echo {value} > "$CPU/{path}"')
124    results = self._ForEachCpu(
125        template.format(path=path, value=value, condition=condition))
126    cpus = ' '.join(cpu for (cpu, _, status) in results if status == 0)
127    if cpus:
128      logger.info('Successfully set %s to %r on: %s', path, value, cpus)
129    else:
130      logger.warning('Failed to set %s to %r on any cpus', path, value)
131
132  def _ReadEachCpuFile(self, path):
133    return self._ForEachCpu(
134        'cat "$CPU/{path}"'.format(path=path))
135
136  def SetScalingGovernor(self, value):
137    """Sets the scaling governor to the given value on all possible CPUs.
138
139    This does not attempt to set a governor to a value not reported as available
140    on the corresponding CPU.
141
142    Args:
143      value: [string] The new governor value.
144    """
145    condition = 'test -e "{path}" && grep -q {value} {path}'.format(
146        path=('${CPU}/%s' % self._AVAILABLE_GOVERNORS_REL_PATH),
147        value=value)
148    self._ConditionallyWriteEachCpuFile(
149        'cpufreq/scaling_governor', value, condition)
150
151  def GetScalingGovernor(self):
152    """Gets the currently set governor for each CPU.
153
154    Returns:
155      An iterable of 2-tuples, each containing the cpu and the current
156      governor.
157    """
158    raw = self._ReadEachCpuFile('cpufreq/scaling_governor')
159    return [
160        (cpu, raw_governor.strip() if not exit_code else None)
161        for cpu, raw_governor, exit_code in raw]
162
163  def ListAvailableGovernors(self):
164    """Returns the list of available governors for each CPU.
165
166    Returns:
167      An iterable of 2-tuples, each containing the cpu and a list of available
168      governors for that cpu.
169    """
170    return self._available_governors
171
172  def _SetScalingMaxFreq(self, value):
173    self._WriteEachCpuFile('cpufreq/scaling_max_freq', '%d' % value)
174
175  def _SetMaxGpuClock(self, value):
176    self._device.WriteFile('/sys/class/kgsl/kgsl-3d0/max_gpuclk',
177                           str(value),
178                           as_root=True)
179
180  def _AllCpusAreOnline(self):
181    results = self._ForEachCpu('cat "$CPU/online"')
182    # TODO(epenner): Investigate why file may be missing
183    # (http://crbug.com/397118)
184    return all(output.rstrip() == '1' and status == 0
185               for (cpu, output, status) in results
186               if cpu != 'cpu0')
187
188  def _ForceAllCpusOnline(self, force_online):
189    """Enable all CPUs on a device.
190
191    Some vendors (or only Qualcomm?) hot-plug their CPUs, which can add noise
192    to measurements:
193    - In perf, samples are only taken for the CPUs that are online when the
194      measurement is started.
195    - The scaling governor can't be set for an offline CPU and frequency scaling
196      on newly enabled CPUs adds noise to both perf and tracing measurements.
197
198    It appears Qualcomm is the only vendor that hot-plugs CPUs, and on Qualcomm
199    this is done by "mpdecision".
200
201    """
202    if self._have_mpdecision:
203      cmd = ['stop', 'mpdecision'] if force_online else ['start', 'mpdecision']
204      self._device.RunShellCommand(cmd, check_return=True, as_root=True)
205
206    if not self._have_mpdecision and not self._AllCpusAreOnline():
207      logger.warning('Unexpected cpu hot plugging detected.')
208
209    if force_online:
210      self._ForEachCpu('echo 1 > "$CPU/online"')
211