Home | History | Annotate | Download | only in crosperf
      1 # Copyright (c) 2013~2015 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 """SuiteRunner defines the interface from crosperf to test script."""
      5 
      6 from __future__ import print_function
      7 
      8 import os
      9 import time
     10 import shlex
     11 
     12 from cros_utils import command_executer
     13 import test_flag
     14 
     15 TEST_THAT_PATH = '/usr/bin/test_that'
     16 AUTOTEST_DIR = '~/trunk/src/third_party/autotest/files'
     17 CHROME_MOUNT_DIR = '/tmp/chrome_root'
     18 
     19 
     20 def GetProfilerArgs(profiler_args):
     21   # Remove "--" from in front of profiler args.
     22   args_list = shlex.split(profiler_args)
     23   new_list = []
     24   for arg in args_list:
     25     if arg[0:2] == '--':
     26       arg = arg[2:]
     27     new_list.append(arg)
     28   args_list = new_list
     29 
     30   # Remove "perf_options=" from middle of profiler args.
     31   new_list = []
     32   for arg in args_list:
     33     idx = arg.find('perf_options=')
     34     if idx != -1:
     35       prefix = arg[0:idx]
     36       suffix = arg[idx + len('perf_options=') + 1:-1]
     37       new_arg = prefix + "'" + suffix + "'"
     38       new_list.append(new_arg)
     39     else:
     40       new_list.append(arg)
     41   args_list = new_list
     42 
     43   return ' '.join(args_list)
     44 
     45 
     46 class SuiteRunner(object):
     47   """This defines the interface from crosperf to test script."""
     48 
     49   def __init__(self,
     50                logger_to_use=None,
     51                log_level='verbose',
     52                cmd_exec=None,
     53                cmd_term=None):
     54     self.logger = logger_to_use
     55     self.log_level = log_level
     56     self._ce = cmd_exec or command_executer.GetCommandExecuter(
     57         self.logger, log_level=self.log_level)
     58     self._ct = cmd_term or command_executer.CommandTerminator()
     59 
     60   def Run(self, machine, label, benchmark, test_args, profiler_args):
     61     for i in range(0, benchmark.retries + 1):
     62       self.PinGovernorExecutionFrequencies(machine, label.chromeos_root)
     63       if benchmark.suite == 'telemetry':
     64         self.DecreaseWaitTime(machine, label.chromeos_root)
     65         ret_tup = self.Telemetry_Run(machine, label, benchmark, profiler_args)
     66       elif benchmark.suite == 'telemetry_Crosperf':
     67         self.DecreaseWaitTime(machine, label.chromeos_root)
     68         ret_tup = self.Telemetry_Crosperf_Run(machine, label, benchmark,
     69                                               test_args, profiler_args)
     70       else:
     71         ret_tup = self.Test_That_Run(machine, label, benchmark, test_args,
     72                                      profiler_args)
     73       if ret_tup[0] != 0:
     74         self.logger.LogOutput('benchmark %s failed. Retries left: %s' %
     75                               (benchmark.name, benchmark.retries - i))
     76       elif i > 0:
     77         self.logger.LogOutput('benchmark %s succeded after %s retries' %
     78                               (benchmark.name, i))
     79         break
     80       else:
     81         self.logger.LogOutput('benchmark %s succeded on first try' %
     82                               benchmark.name)
     83         break
     84     return ret_tup
     85 
     86   def PinGovernorExecutionFrequencies(self, machine_name, chromeos_root):
     87     """Set min and max frequencies to max static frequency."""
     88     # pyformat: disable
     89     set_cpu_freq = (
     90         'set -e && '
     91         'for f in /sys/devices/system/cpu/cpu*/cpufreq; do '
     92         'cd $f; '
     93         'val=0; '
     94         'if [[ -e scaling_available_frequencies ]]; then '
     95         # pylint: disable=line-too-long
     96         '  val=`cat scaling_available_frequencies | tr " " "\\n" | sort -n -b -r`; '
     97         'else '
     98         '  val=`cat scaling_max_freq | tr " " "\\n" | sort -n -b -r`; fi ; '
     99         'set -- $val; '
    100         'highest=$1; '
    101         'if [[ $# -gt 1 ]]; then '
    102         '  case $highest in *1000) highest=$2;; esac; '
    103         'fi ;'
    104         'echo $highest > scaling_max_freq; '
    105         'echo $highest > scaling_min_freq; '
    106         'echo performance > scaling_governor; '
    107         'done'
    108     )
    109     # pyformat: enable
    110     if self.log_level == 'average':
    111       self.logger.LogOutput('Pinning governor execution frequencies for %s' %
    112                             machine_name)
    113     ret = self._ce.CrosRunCommand(
    114         set_cpu_freq, machine=machine_name, chromeos_root=chromeos_root)
    115     self.logger.LogFatalIf(ret, 'Could not pin frequencies on machine: %s' %
    116                            machine_name)
    117 
    118   def DecreaseWaitTime(self, machine_name, chromeos_root):
    119     """Change the ten seconds wait time for pagecycler to two seconds."""
    120     FILE = '/usr/local/telemetry/src/tools/perf/page_sets/page_cycler_story.py'
    121     ret = self._ce.CrosRunCommand(
    122         'ls ' + FILE, machine=machine_name, chromeos_root=chromeos_root)
    123     self.logger.LogFatalIf(ret, 'Could not find {} on machine: {}'.format(
    124         FILE, machine_name))
    125 
    126     if not ret:
    127       sed_command = 'sed -i "s/_TTI_WAIT_TIME = 10/_TTI_WAIT_TIME = 2/g" '
    128       ret = self._ce.CrosRunCommand(
    129           sed_command + FILE, machine=machine_name, chromeos_root=chromeos_root)
    130       self.logger.LogFatalIf(ret, 'Could not modify {} on machine: {}'.format(
    131           FILE, machine_name))
    132 
    133   def RebootMachine(self, machine_name, chromeos_root):
    134     command = 'reboot && exit'
    135     self._ce.CrosRunCommand(
    136         command, machine=machine_name, chromeos_root=chromeos_root)
    137     time.sleep(60)
    138     # Whenever we reboot the machine, we need to restore the governor settings.
    139     self.PinGovernorExecutionFrequencies(machine_name, chromeos_root)
    140 
    141   def Test_That_Run(self, machine, label, benchmark, test_args, profiler_args):
    142     """Run the test_that test.."""
    143     options = ''
    144     if label.board:
    145       options += ' --board=%s' % label.board
    146     if test_args:
    147       options += ' %s' % test_args
    148     if profiler_args:
    149       self.logger.LogFatal('test_that does not support profiler.')
    150     command = 'rm -rf /usr/local/autotest/results/*'
    151     self._ce.CrosRunCommand(
    152         command, machine=machine, chromeos_root=label.chromeos_root)
    153 
    154     # We do this because some tests leave the machine in weird states.
    155     # Rebooting between iterations has proven to help with this.
    156     self.RebootMachine(machine, label.chromeos_root)
    157 
    158     autotest_dir = AUTOTEST_DIR
    159     if label.autotest_path != '':
    160       autotest_dir = label.autotest_path
    161 
    162     autotest_dir_arg = '--autotest_dir %s' % autotest_dir
    163     # For non-telemetry tests, specify an autotest directory only if the
    164     # specified directory is different from default (crosbug.com/679001).
    165     if autotest_dir == AUTOTEST_DIR:
    166       autotest_dir_arg = ''
    167 
    168     command = (('%s %s --fast '
    169                 '%s %s %s') % (TEST_THAT_PATH, autotest_dir_arg, options,
    170                                machine, benchmark.test_name))
    171     if self.log_level != 'verbose':
    172       self.logger.LogOutput('Running test.')
    173       self.logger.LogOutput('CMD: %s' % command)
    174     # Use --no-ns-pid so that cros_sdk does not create a different
    175     # process namespace and we can kill process created easily by
    176     # their process group.
    177     return self._ce.ChrootRunCommandWOutput(
    178         label.chromeos_root,
    179         command,
    180         command_terminator=self._ct,
    181         cros_sdk_options='--no-ns-pid')
    182 
    183   def RemoveTelemetryTempFile(self, machine, chromeos_root):
    184     filename = 'telemetry@%s' % machine
    185     fullname = os.path.join(chromeos_root, 'chroot', 'tmp', filename)
    186     if os.path.exists(fullname):
    187       os.remove(fullname)
    188 
    189   def Telemetry_Crosperf_Run(self, machine, label, benchmark, test_args,
    190                              profiler_args):
    191     if not os.path.isdir(label.chrome_src):
    192       self.logger.LogFatal('Cannot find chrome src dir to'
    193                            ' run telemetry: %s' % label.chrome_src)
    194 
    195     # Check for and remove temporary file that may have been left by
    196     # previous telemetry runs (and which might prevent this run from
    197     # working).
    198     self.RemoveTelemetryTempFile(machine, label.chromeos_root)
    199 
    200     # For telemetry runs, we can use the autotest copy from the source
    201     # location. No need to have one under /build/<board>.
    202     autotest_dir_arg = '--autotest_dir %s' % AUTOTEST_DIR
    203     if label.autotest_path != '':
    204       autotest_dir_arg = '--autotest_dir %s' % label.autotest_path
    205 
    206     profiler_args = GetProfilerArgs(profiler_args)
    207     fast_arg = ''
    208     if not profiler_args:
    209       # --fast works unless we are doing profiling (autotest limitation).
    210       # --fast avoids unnecessary copies of syslogs.
    211       fast_arg = '--fast'
    212     args_string = ''
    213     if test_args:
    214       # Strip double quotes off args (so we can wrap them in single
    215       # quotes, to pass through to Telemetry).
    216       if test_args[0] == '"' and test_args[-1] == '"':
    217         test_args = test_args[1:-1]
    218       args_string = "test_args='%s'" % test_args
    219 
    220     cmd = ('{} {} {} --board={} --args="{} run_local={} test={} '
    221            '{}" {} telemetry_Crosperf'.format(TEST_THAT_PATH, autotest_dir_arg,
    222                                               fast_arg, label.board,
    223                                               args_string, benchmark.run_local,
    224                                               benchmark.test_name,
    225                                               profiler_args, machine))
    226 
    227     # Use --no-ns-pid so that cros_sdk does not create a different
    228     # process namespace and we can kill process created easily by their
    229     # process group.
    230     chrome_root_options = ('--no-ns-pid '
    231                            '--chrome_root={} --chrome_root_mount={} '
    232                            "FEATURES=\"-usersandbox\" "
    233                            'CHROME_ROOT={}'.format(label.chrome_src,
    234                                                    CHROME_MOUNT_DIR,
    235                                                    CHROME_MOUNT_DIR))
    236     if self.log_level != 'verbose':
    237       self.logger.LogOutput('Running test.')
    238       self.logger.LogOutput('CMD: %s' % cmd)
    239     return self._ce.ChrootRunCommandWOutput(
    240         label.chromeos_root,
    241         cmd,
    242         command_terminator=self._ct,
    243         cros_sdk_options=chrome_root_options)
    244 
    245   def Telemetry_Run(self, machine, label, benchmark, profiler_args):
    246     telemetry_run_path = ''
    247     if not os.path.isdir(label.chrome_src):
    248       self.logger.LogFatal('Cannot find chrome src dir to' ' run telemetry.')
    249     else:
    250       telemetry_run_path = os.path.join(label.chrome_src, 'src/tools/perf')
    251       if not os.path.exists(telemetry_run_path):
    252         self.logger.LogFatal('Cannot find %s directory.' % telemetry_run_path)
    253 
    254     if profiler_args:
    255       self.logger.LogFatal('Telemetry does not support the perf profiler.')
    256 
    257     # Check for and remove temporary file that may have been left by
    258     # previous telemetry runs (and which might prevent this run from
    259     # working).
    260     if not test_flag.GetTestMode():
    261       self.RemoveTelemetryTempFile(machine, label.chromeos_root)
    262 
    263     rsa_key = os.path.join(
    264         label.chromeos_root,
    265         'src/scripts/mod_for_test_scripts/ssh_keys/testing_rsa')
    266 
    267     cmd = ('cd {0} && '
    268            './run_measurement '
    269            '--browser=cros-chrome '
    270            '--output-format=csv '
    271            '--remote={1} '
    272            '--identity {2} '
    273            '{3} {4}'.format(telemetry_run_path, machine, rsa_key,
    274                             benchmark.test_name, benchmark.test_args))
    275     if self.log_level != 'verbose':
    276       self.logger.LogOutput('Running test.')
    277       self.logger.LogOutput('CMD: %s' % cmd)
    278     return self._ce.RunCommandWOutput(cmd, print_to_console=False)
    279 
    280   def CommandTerminator(self):
    281     return self._ct
    282 
    283   def Terminate(self):
    284     self._ct.Terminate()
    285 
    286 
    287 class MockSuiteRunner(object):
    288   """Mock suite runner for test."""
    289 
    290   def __init__(self):
    291     self._true = True
    292 
    293   def Run(self, *_args):
    294     if self._true:
    295       return [0, '', '']
    296     else:
    297       return [0, '', '']
    298