Home | History | Annotate | Download | only in telemetry_AFDOGenerate
      1 # Copyright (c) 2013 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 
      5 """
      6 Test to generate the AFDO profile for a set of ChromeOS benchmarks.
      7 
      8 This will run a pre-determined set of benchmarks on the DUT under
      9 the monitoring of the linux "perf" tool. The resulting perf.data
     10 file will then be copied to Google Storage (GS) where it can be
     11 used by the AFDO optimized build.
     12 
     13 Given that the telemetry benchmarks are quite unstable on ChromeOS at
     14 this point, this test also supports a mode where the benchmarks are
     15 executed outside of the telemetry framework. It is not the same as
     16 executing the benchmarks under telemetry because there is no telemetry
     17 measurement taken but, for the purposes of profiling Chrome, it should
     18 be pretty close.
     19 
     20 Example invocation:
     21 /usr/bin/test_that --debug --board=lumpy <DUT IP>
     22   --args="ignore_failures=True local=True gs_test_location=True"
     23   telemetry_AFDOGenerate
     24 """
     25 
     26 import bz2
     27 import logging
     28 import os
     29 import time
     30 
     31 from autotest_lib.client.common_lib import error, utils
     32 from autotest_lib.server import autotest
     33 from autotest_lib.server import profilers
     34 from autotest_lib.server import test
     35 from autotest_lib.server import utils
     36 from autotest_lib.server.cros import telemetry_runner
     37 
     38 # List of benchmarks to run to capture profile information. This is
     39 # based on the "superhero" and "perf_v2" list and other telemetry
     40 # benchmarks. Goal is to have a short list that is as representative
     41 # as possible and takes a short time to execute. At this point the
     42 # list of benchmarks is in flux.
     43 TELEMETRY_AFDO_BENCHMARKS = (
     44     ('page_cycler.typical_25', ('--pageset-repeat=2',)),
     45     ('page_cycler.intl_ja_zh', ('--pageset-repeat=1',)),
     46     ('page_cycler.intl_ar_fa_he', ('--pageset-repeat=1',)),
     47     ('page_cycler.intl_es_fr_pt-BR', ('--pageset-repeat=1',)),
     48     ('page_cycler.intl_ko_th_vi', ('--pageset-repeat=1',)),
     49     ('page_cycler.intl_hi_ru', ('--pageset-repeat=1',)),
     50     ('octane',),
     51     ('kraken',),
     52     ('speedometer',),
     53     ('dromaeo.domcoreattr',),
     54     ('dromaeo.domcoremodify',),
     55     ('smoothness.tough_webgl_cases',)
     56     )
     57 
     58 # Some benchmarks removed from the profile set:
     59 # 'page_cycler.morejs' -> uninteresting, seems to fail frequently,
     60 # 'page_cycler.moz' -> seems very old.
     61 # 'media.tough_video_cases' -> removed this because it does not bring
     62 #                              any benefit and takes more than 12 mins
     63 
     64 # List of boards where this test can be run.
     65 # Currently, this has only been tested on 'sandybridge' boards.
     66 VALID_BOARDS = ['butterfly', 'lumpy', 'parrot', 'stumpy']
     67 
     68 class telemetry_AFDOGenerate(test.test):
     69     """
     70     Run one or more telemetry benchmarks under the "perf" monitoring
     71     tool, generate a "perf.data" file and upload to GS for comsumption
     72     by the AFDO optimized build.
     73     """
     74     version = 1
     75 
     76 
     77     def run_once(self, host, args):
     78         """Run a set of telemetry benchmarks.
     79 
     80         @param host: Host machine where test is run
     81         @param args: A dictionary of the arguments that were passed
     82                 to this test.
     83         @returns None.
     84         """
     85         self._host = host
     86         host_board = host.get_board().split(':')[1]
     87         if not host_board in VALID_BOARDS:
     88             raise error.TestFail(
     89                     'This test cannot be run on board %s' % host_board)
     90 
     91         self._parse_args(args)
     92 
     93         if self._minimal_telemetry:
     94             self._run_tests_minimal_telemetry()
     95         else:
     96             self._telemetry_runner = telemetry_runner.TelemetryRunner(
     97                     self._host, self._local)
     98 
     99             for benchmark_info in TELEMETRY_AFDO_BENCHMARKS:
    100                 benchmark = benchmark_info[0]
    101                 args = () if len(benchmark_info) == 1 else benchmark_info[1]
    102                 try:
    103                     self._run_test_with_retry(benchmark, *args)
    104                 except error.TestBaseException:
    105                     if not self._ignore_failures:
    106                         raise
    107                     else:
    108                         logging.info('Ignoring failure from benchmark %s.',
    109                                      benchmark)
    110 
    111 
    112     def after_run_once(self):
    113         """After the profile information has been collected, compress it
    114         and upload it to GS
    115         """
    116         PERF_FILE = 'perf.data'
    117         COMP_PERF_FILE = 'chromeos-chrome-%s-%s.perf.data'
    118         perf_data = os.path.join(self.profdir, PERF_FILE)
    119         comp_data = os.path.join(self.profdir, COMP_PERF_FILE % (
    120                 self._arch, self._version))
    121         compressed = self._compress_file(perf_data, comp_data)
    122         self._gs_upload(compressed, os.path.basename(compressed))
    123 
    124         # Also create copy of this file using "LATEST" as version so
    125         # it can be found in case the builder is looking for a version
    126         # number that does not match. It is ok to use a slighly old
    127         # version of the this file for the optimized build
    128         latest_data =  COMP_PERF_FILE % (self._arch, 'LATEST')
    129         latest_compressed = self._get_compressed_name(latest_data)
    130         self._gs_upload(compressed, latest_compressed)
    131 
    132 
    133     def _parse_args(self, args):
    134         """Parses input arguments to this autotest.
    135 
    136         @param args: Options->values dictionary.
    137         @raises error.TestFail if a bad option is passed.
    138         """
    139 
    140         # Set default values for the options.
    141         # Architecture for which we are collecting afdo data.
    142         self._arch = 'amd64'
    143         # Use an alternate GS location where everyone can write.
    144         # Set default depending on whether this is executing in
    145         # the lab environment or not
    146         self._gs_test_location = not utils.host_is_in_lab_zone(
    147                 self._host.hostname)
    148         # Ignore individual test failures.
    149         self._ignore_failures = False
    150         # Use local copy of telemetry instead of using the dev server copy.
    151         self._local = False
    152         # Chrome version to which the AFDO data corresponds.
    153         self._version, _ = self._host.get_chrome_version()
    154         # Try to use the minimal support from Telemetry. The Telemetry
    155         # benchmarks in ChromeOS are too flaky at this point. So, initially,
    156         # this will be set to True by default.
    157         self._minimal_telemetry = False
    158 
    159         for option_name, value in args.iteritems():
    160             if option_name == 'arch':
    161                 self._arch = value
    162             elif option_name == 'gs_test_location':
    163                 self._gs_test_location = (value == 'True')
    164             elif option_name == 'ignore_failures':
    165                 self._ignore_failures = (value == 'True')
    166             elif option_name == 'local':
    167                 self._local = (value == 'True')
    168             elif option_name == 'minimal_telemetry':
    169                 self._minimal_telemetry = (value == 'True')
    170             elif option_name == 'version':
    171                 self._version = value
    172             else:
    173                 raise error.TestFail('Unknown option passed: %s' % option_name)
    174 
    175 
    176     def _run_test(self, benchmark, *args):
    177         """Run the benchmark using Telemetry.
    178 
    179         @param benchmark: Name of the benchmark to run.
    180         @param args: Additional arguments to pass to the telemetry execution
    181                      script.
    182         @raises Raises error.TestFail if execution of test failed.
    183                 Also re-raise any exceptions thrown by run_telemetry benchmark.
    184         """
    185         try:
    186             logging.info('Starting run for Telemetry benchmark %s', benchmark)
    187             start_time = time.time()
    188             result = self._telemetry_runner.run_telemetry_benchmark(
    189                     benchmark, None, *args)
    190             end_time = time.time()
    191             logging.info('Completed Telemetry benchmark %s in %f seconds',
    192                          benchmark, end_time - start_time)
    193         except error.TestBaseException as e:
    194             end_time = time.time()
    195             logging.info('Got exception from Telemetry benchmark %s '
    196                          'after %f seconds. Exception: %s',
    197                          benchmark, end_time - start_time, str(e))
    198             raise
    199 
    200         # We dont generate any keyvals for this run. This is not
    201         # an official run of the benchmark. We are just running it to get
    202         # a profile from it.
    203 
    204         if result.status is telemetry_runner.SUCCESS_STATUS:
    205             logging.info('Benchmark %s succeeded', benchmark)
    206         else:
    207             raise error.TestFail('An error occurred while executing'
    208                                  ' benchmark: %s' % benchmark)
    209 
    210 
    211     def _run_test_with_retry(self, benchmark, *args):
    212         """Run the benchmark using Telemetry. Retry in case of failure.
    213 
    214         @param benchmark: Name of the benchmark to run.
    215         @param args: Additional arguments to pass to the telemetry execution
    216                      script.
    217         @raises Re-raise any exceptions thrown by _run_test.
    218         """
    219 
    220         tried = False
    221         while True:
    222             try:
    223                 self._run_test(benchmark, *args)
    224                 logging.info('Benchmark %s succeeded on %s try',
    225                              benchmark,
    226                              'first' if not tried else 'second')
    227                 break
    228             except error.TestBaseException:
    229                 if not tried:
    230                    tried = True
    231                    logging.info('Benchmark %s failed. Retrying ...',
    232                                 benchmark)
    233                 else:
    234                     logging.info('Benchmark %s failed twice. Not retrying',
    235                                   benchmark)
    236                     raise
    237 
    238 
    239     def _run_tests_minimal_telemetry(self):
    240         """Run the benchmarks using the minimal support from Telemetry.
    241 
    242         The benchmarks are run using a client side autotest test. This test
    243         will control Chrome directly using the chrome.Chrome support and it
    244         will ask Chrome to display the benchmark pages directly instead of
    245         using the "page sets" and "measurements" support from Telemetry.
    246         In this way we avoid using Telemetry benchmark support which is not
    247         stable on ChromeOS yet.
    248         """
    249         AFDO_GENERATE_CLIENT_TEST = 'telemetry_AFDOGenerateClient'
    250 
    251         # We dont want to "inherit" the profiler settings for this test
    252         # to the client test. Doing so will end up in two instances of
    253         # the profiler (perf) being executed at the same time.
    254         # Filed a feature request about this. See crbug/342958.
    255 
    256         # Save the current settings for profilers.
    257         saved_profilers = self.job.profilers
    258         saved_default_profile_only = self.job.default_profile_only
    259 
    260         # Reset the state of the profilers.
    261         self.job.default_profile_only = False
    262         self.job.profilers = profilers.profilers(self.job)
    263 
    264         # Execute the client side test.
    265         client_at = autotest.Autotest(self._host)
    266         client_at.run_test(AFDO_GENERATE_CLIENT_TEST, args='')
    267 
    268         # Restore the settings for the profilers.
    269         self.job.default_profile_only = saved_default_profile_only
    270         self.job.profiler = saved_profilers
    271 
    272 
    273     @staticmethod
    274     def _get_compressed_name(name):
    275         """Given a file name, return bz2 compressed name.
    276         @param name: Name of uncompressed file.
    277         @returns name of compressed file.
    278         """
    279         return name + '.bz2'
    280 
    281     @staticmethod
    282     def _compress_file(unc_file, com_file):
    283         """Compresses specified file with bz2.
    284 
    285         @param unc_file: name of file to compress.
    286         @param com_file: prefix name of compressed file.
    287         @raises error.TestFail if compression failed
    288         @returns Name of compressed file.
    289         """
    290         dest = ''
    291         with open(unc_file, 'r') as inp:
    292             dest = telemetry_AFDOGenerate._get_compressed_name(com_file)
    293             with bz2.BZ2File(dest, 'w') as out:
    294                 for data in inp:
    295                     out.write(data)
    296         if not dest or not os.path.isfile(dest):
    297             raise error.TestFail('Could not compress %s' % unc_file)
    298         return dest
    299 
    300 
    301     def _gs_upload(self, local_file, remote_basename):
    302         """Uploads file to google storage specific location.
    303 
    304         @param local_file: name of file to upload.
    305         @param remote_basename: basename of remote file.
    306         @raises error.TestFail if upload failed.
    307         @returns nothing.
    308         """
    309         GS_DEST = 'gs://chromeos-prebuilt/afdo-job/canonicals/%s'
    310         GS_TEST_DEST = 'gs://chromeos-throw-away-bucket/afdo-job/canonicals/%s'
    311         GS_ACL = 'project-private'
    312 
    313         gs_dest = GS_TEST_DEST if self._gs_test_location else GS_DEST
    314         remote_file = gs_dest % remote_basename
    315 
    316         logging.info('About to upload to GS: %s', remote_file)
    317         if not utils.gs_upload(local_file,
    318                                remote_file,
    319                                GS_ACL, result_dir=self.resultsdir):
    320             logging.info('Failed upload to GS: %s', remote_file)
    321             raise error.TestFail('Unable to gs upload %s to %s' %
    322                                  (local_file, remote_file))
    323 
    324         logging.info('Successfull upload to GS: %s', remote_file)
    325