1d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved.
2d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
3d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)# found in the LICENSE file.
4d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
55f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import atexit
6d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)import logging
7f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
8a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochfrom pylib import android_commands
9a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochfrom pylib.device import device_utils
10d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
11d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)class PerfControl(object):
12d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  """Provides methods for setting the performance mode of a device."""
13d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  _SCALING_GOVERNOR_FMT = (
14d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor')
156d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  _CPU_ONLINE_FMT = '/sys/devices/system/cpu/cpu%d/online'
16d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  _KERNEL_MAX = '/sys/devices/system/cpu/kernel_max'
17d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
18a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  def __init__(self, device):
19a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    # TODO(jbudorick) Remove once telemetry gets switched over.
20a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    if isinstance(device, android_commands.AndroidCommands):
21a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch      device = device_utils.DeviceUtils(device)
22a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    self._device = device
23116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    cpu_files = self._device.RunShellCommand(
24116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      'ls -d /sys/devices/system/cpu/cpu[0-9]*')
25116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    self._num_cpu_cores = len(cpu_files)
26116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    assert self._num_cpu_cores > 0, 'Failed to detect CPUs.'
27116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    logging.info('Number of CPUs: %d', self._num_cpu_cores)
28116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    self._have_mpdecision = self._device.FileExists('/system/bin/mpdecision')
29d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
30d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  def SetHighPerfMode(self):
315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """Sets the highest possible performance mode for the device."""
325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if not self._device.old_interface.IsRootEnabled():
335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      message = 'Need root for performance mode. Results may be NOISY!!'
345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      logging.warning(message)
355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # Add an additional warning at exit, such that it's clear that any results
365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # may be different/noisy (due to the lack of intended performance mode).
375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      atexit.register(logging.warning, message)
385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      return
396d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    # TODO(epenner): Enable on all devices (http://crbug.com/383566)
406d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    if 'Nexus 4' == self._device.old_interface.GetProductModel():
416d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      self._ForceAllCpusOnline(True)
42116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      if not self._AllCpusAreOnline():
435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        logging.warning('Failed to force CPUs online. Results may be NOISY!')
446d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    self._SetScalingGovernorInternal('performance')
456d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)
466d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def SetPerfProfilingMode(self):
475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """Enables all cores for reliable perf profiling."""
486d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    self._ForceAllCpusOnline(True)
49d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    self._SetScalingGovernorInternal('performance')
50116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    if not self._AllCpusAreOnline():
51116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      if not self._device.old_interface.IsRootEnabled():
52116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        raise RuntimeError('Need root to force CPUs online.')
53116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      raise RuntimeError('Failed to force CPUs online.')
54d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
55d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  def SetDefaultPerfMode(self):
56d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    """Sets the performance mode for the device to its default mode."""
575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if not self._device.old_interface.IsRootEnabled():
585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      return
59116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    product_model = self._device.GetProp('ro.product.model')
60d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    governor_mode = {
61d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        'GT-I9300': 'pegasusq',
62d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        'Galaxy Nexus': 'interactive',
63d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        'Nexus 4': 'ondemand',
64d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        'Nexus 7': 'interactive',
65d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        'Nexus 10': 'interactive'
66d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    }.get(product_model, 'ondemand')
67d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    self._SetScalingGovernorInternal(governor_mode)
686d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    self._ForceAllCpusOnline(False)
69d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
70d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  def _SetScalingGovernorInternal(self, value):
711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    cpu_cores = ' '.join([str(x) for x in range(self._num_cpu_cores)])
721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    script = ('for CPU in %s; do\n'
731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        '  FILE="/sys/devices/system/cpu/cpu$CPU/cpufreq/scaling_governor"\n'
741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        '  test -e $FILE && echo %s > $FILE\n'
751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        'done\n') % (cpu_cores, value)
761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    logging.info('Setting scaling governor mode: %s', value)
771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self._device.RunShellCommand(script, as_root=True)
78a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
796d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def _AllCpusAreOnline(self):
801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    for cpu in range(1, self._num_cpu_cores):
816d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      online_path = PerfControl._CPU_ONLINE_FMT % cpu
825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # TODO(epenner): Investigate why file may be missing
835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # (http://crbug.com/397118)
845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      if not self._device.FileExists(online_path) or \
855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self._device.ReadFile(online_path)[0] == '0':
866d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)        return False
876d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    return True
886d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)
896d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def _ForceAllCpusOnline(self, force_online):
906d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    """Enable all CPUs on a device.
91f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
926d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    Some vendors (or only Qualcomm?) hot-plug their CPUs, which can add noise
936d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    to measurements:
946d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    - In perf, samples are only taken for the CPUs that are online when the
956d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      measurement is started.
966d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    - The scaling governor can't be set for an offline CPU and frequency scaling
976d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      on newly enabled CPUs adds noise to both perf and tracing measurements.
986d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)
996d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    It appears Qualcomm is the only vendor that hot-plugs CPUs, and on Qualcomm
1006d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    this is done by "mpdecision".
101f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
102f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    """
1036d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    if self._have_mpdecision:
1046d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      script = 'stop mpdecision' if force_online else 'start mpdecision'
105116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      self._device.RunShellCommand(script, as_root=True)
1066d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)
1076d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    if not self._have_mpdecision and not self._AllCpusAreOnline():
1086d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      logging.warning('Unexpected cpu hot plugging detected.')
1096d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)
1106d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    if not force_online:
1116d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      return
1126d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)
113116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    for cpu in range(self._num_cpu_cores):
1146d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      online_path = PerfControl._CPU_ONLINE_FMT % cpu
115116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      self._device.WriteFile(online_path, '1', as_root=True)
116