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.
8 from pylib
import android_commands
9 from pylib
.device
import device_utils
11 class PerfControl(object):
12 """Provides methods for setting the performance mode of a device."""
13 _CPU_PATH
= '/sys/devices/system/cpu'
14 _KERNEL_MAX
= '/sys/devices/system/cpu/kernel_max'
16 def __init__(self
, device
):
17 # TODO(jbudorick) Remove once telemetry gets switched over.
18 if isinstance(device
, android_commands
.AndroidCommands
):
19 device
= device_utils
.DeviceUtils(device
)
21 # this will raise an AdbCommandFailedError if no CPU files are found
22 self
._cpu
_files
= self
._device
.RunShellCommand(
23 'ls -d cpu[0-9]*', cwd
=self
._CPU
_PATH
, check_return
=True, as_root
=True)
24 assert self
._cpu
_files
, 'Failed to detect CPUs.'
25 self
._cpu
_file
_list
= ' '.join(self
._cpu
_files
)
26 logging
.info('CPUs found: %s', self
._cpu
_file
_list
)
27 self
._have
_mpdecision
= self
._device
.FileExists('/system/bin/mpdecision')
29 def SetHighPerfMode(self
):
30 """Sets the highest stable performance mode for the device."""
31 if not self
._device
.old_interface
.IsRootEnabled():
32 message
= 'Need root for performance mode. Results may be NOISY!!'
33 logging
.warning(message
)
34 # Add an additional warning at exit, such that it's clear that any results
35 # may be different/noisy (due to the lack of intended performance mode).
36 atexit
.register(logging
.warning
, message
)
39 product_model
= self
._device
.product_model
40 # TODO(epenner): Enable on all devices (http://crbug.com/383566)
41 if 'Nexus 4' == product_model
:
42 self
._ForceAllCpusOnline
(True)
43 if not self
._AllCpusAreOnline
():
44 logging
.warning('Failed to force CPUs online. Results may be NOISY!')
45 self
._SetScalingGovernorInternal
('performance')
46 elif 'Nexus 5' == product_model
:
47 self
._ForceAllCpusOnline
(True)
48 if not self
._AllCpusAreOnline
():
49 logging
.warning('Failed to force CPUs online. Results may be NOISY!')
50 self
._SetScalingGovernorInternal
('performance')
51 self
._SetScalingMaxFreq
(1190400)
52 self
._SetMaxGpuClock
(200000000)
54 self
._SetScalingGovernorInternal
('performance')
56 def SetPerfProfilingMode(self
):
57 """Enables all cores for reliable perf profiling."""
58 self
._ForceAllCpusOnline
(True)
59 self
._SetScalingGovernorInternal
('performance')
60 if not self
._AllCpusAreOnline
():
61 if not self
._device
.old_interface
.IsRootEnabled():
62 raise RuntimeError('Need root to force CPUs online.')
63 raise RuntimeError('Failed to force CPUs online.')
65 def SetDefaultPerfMode(self
):
66 """Sets the performance mode for the device to its default mode."""
67 if not self
._device
.old_interface
.IsRootEnabled():
69 product_model
= self
._device
.product_model
70 if 'Nexus 5' == product_model
:
71 if self
._AllCpusAreOnline
():
72 self
._SetScalingMaxFreq
(2265600)
73 self
._SetMaxGpuClock
(450000000)
76 'GT-I9300': 'pegasusq',
77 'Galaxy Nexus': 'interactive',
78 'Nexus 4': 'ondemand',
79 'Nexus 5': 'ondemand',
80 'Nexus 7': 'interactive',
81 'Nexus 10': 'interactive'
82 }.get(product_model
, 'ondemand')
83 self
._SetScalingGovernorInternal
(governor_mode
)
84 self
._ForceAllCpusOnline
(False)
87 online
= (output
.rstrip() == '1' and status
== 0
88 for (_
, output
, status
) in self
._ForEachCpu
('cat "$CPU/online"'))
89 governor
= (output
.rstrip() if status
== 0 else None
90 for (_
, output
, status
)
91 in self
._ForEachCpu
('cat "$CPU/cpufreq/scaling_governor"'))
92 return zip(self
._cpu
_files
, online
, governor
)
94 def _ForEachCpu(self
, cmd
):
96 'for CPU in %s' % self
._cpu
_file
_list
,
101 output
= self
._device
.RunShellCommand(
102 script
, cwd
=self
._CPU
_PATH
, check_return
=True, as_root
=True)
103 output
= '\n'.join(output
).split('%~%')
104 return zip(self
._cpu
_files
, output
[0::2], (int(c
) for c
in output
[1::2]))
106 def _WriteEachCpuFile(self
, path
, value
):
107 results
= self
._ForEachCpu
(
108 'test -e "$CPU/{path}" && echo {value} > "$CPU/{path}"'.format(
109 path
=path
, value
=value
))
110 cpus
= ' '.join(cpu
for (cpu
, _
, status
) in results
if status
== 0)
112 logging
.info('Successfully set %s to %r on: %s', path
, value
, cpus
)
114 logging
.warning('Failed to set %s to %r on any cpus')
116 def _SetScalingGovernorInternal(self
, value
):
117 self
._WriteEachCpuFile
('cpufreq/scaling_governor', value
)
119 def _SetScalingMaxFreq(self
, value
):
120 self
._WriteEachCpuFile
('cpufreq/scaling_max_freq', '%d' % value
)
122 def _SetMaxGpuClock(self
, value
):
123 self
._device
.WriteFile('/sys/class/kgsl/kgsl-3d0/max_gpuclk',
127 def _AllCpusAreOnline(self
):
128 results
= self
._ForEachCpu
('cat "$CPU/online"')
129 # TODO(epenner): Investigate why file may be missing
130 # (http://crbug.com/397118)
131 return all(output
.rstrip() == '1' and status
== 0
132 for (cpu
, output
, status
) in results
135 def _ForceAllCpusOnline(self
, force_online
):
136 """Enable all CPUs on a device.
138 Some vendors (or only Qualcomm?) hot-plug their CPUs, which can add noise
140 - In perf, samples are only taken for the CPUs that are online when the
141 measurement is started.
142 - The scaling governor can't be set for an offline CPU and frequency scaling
143 on newly enabled CPUs adds noise to both perf and tracing measurements.
145 It appears Qualcomm is the only vendor that hot-plugs CPUs, and on Qualcomm
146 this is done by "mpdecision".
149 if self
._have
_mpdecision
:
150 script
= 'stop mpdecision' if force_online
else 'start mpdecision'
151 self
._device
.RunShellCommand(script
, check_return
=True, as_root
=True)
153 if not self
._have
_mpdecision
and not self
._AllCpusAreOnline
():
154 logging
.warning('Unexpected cpu hot plugging detected.')
157 self
._ForEachCpu
('echo 1 > "$CPU/online"')