Home | History | Annotate | Download | only in platform_Perf
      1 # Copyright (c) 2014 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 import errno, os, re, subprocess
      5 from autotest_lib.client.bin import test
      6 from autotest_lib.client.common_lib import error
      7 
      8 
      9 class platform_Perf(test.test):
     10     """
     11     Gathers perf data and makes sure it is well-formed.
     12     """
     13     version = 1
     14 
     15     # Whitelist of DSOs that are expected to appear in perf data from a CrOS
     16     # device. The actual name may change so use regex pattern matching. This is
     17     # a list of allowed but not required DSOs. With this list, we can filter out
     18     # unknown DSOs that might not have a build ID, e.g. JIT code.
     19     _KERNEL_NAME_REGEX = re.compile(r'.*kernel\.kallsyms.*')
     20     _DSO_WHITELIST_REGEX = [
     21       _KERNEL_NAME_REGEX,
     22       re.compile(r'bash'),
     23       re.compile(r'chrome'),
     24       re.compile(r'ld-.*\.so.*'),
     25       # For simplicity since the libbase binaries are built together, we assume
     26       # that if one of them (libbase-core) was properly built and passes this
     27       # test, then the others will pass as well. It's easier than trying to
     28       # include all libbase-* while filtering out libbase-XXXXXX.so, which is a
     29       # text file that links to the other files.
     30       re.compile(r'libbase-core-.*\.so.*'),
     31       re.compile(r'libc-.*\.so.*'),
     32       re.compile(r'libdbus-.*\.so.*'),
     33       re.compile(r'libpthread-.*\.so.*'),
     34       re.compile(r'libstdc\+\+.*\.so\..*'),
     35     ]
     36 
     37 
     38     def run_once(self):
     39         """
     40         Collect a perf data profile and check the detailed perf report.
     41         """
     42         keyvals = {}
     43         num_errors = 0
     44 
     45         try:
     46             # Create temporary file and get its name. Then close it.
     47             perf_file_path = os.tempnam()
     48 
     49             # Perf command for recording a profile.
     50             perf_record_args = [ 'perf', 'record', '-a', '-o', perf_file_path,
     51                                  '--', 'sleep', '2']
     52             # Perf command for getting a detailed report.
     53             perf_report_args = [ 'perf', 'report', '-D', '-i', perf_file_path ]
     54             # Perf command for getting a report grouped by DSO name.
     55             perf_report_dso_args = [ 'perf', 'report', '--sort', 'dso', '-i',
     56                                      perf_file_path ]
     57             # Perf command for getting a list of all build IDs in a data file.
     58             perf_buildid_list_args = [ 'perf', 'buildid-list', '-i',
     59                                        perf_file_path ]
     60 
     61             try:
     62                 subprocess.check_output(perf_record_args,
     63                                         stderr=subprocess.STDOUT)
     64             except subprocess.CalledProcessError as cmd_error:
     65                 raise error.TestFail("Running command [%s] failed: %s" %
     66                                      (' '.join(perf_record_args),
     67                                       cmd_error.output))
     68 
     69             # Make sure the file still exists.
     70             if not os.path.isfile(perf_file_path):
     71                 raise error.TestFail('Could not find perf output file: ' +
     72                                      perf_file_path)
     73 
     74             # Get detailed perf data view and extract the line containing the
     75             # kernel MMAP summary.
     76             kernel_mapping = None
     77             p = subprocess.Popen(perf_report_args, stdout=subprocess.PIPE)
     78             for line in p.stdout:
     79                 if ('PERF_RECORD_MMAP' in line and
     80                     self._KERNEL_NAME_REGEX.match(line)):
     81                     kernel_mapping = line
     82                     break
     83 
     84             # Read the rest of output to EOF.
     85             for _ in p.stdout:
     86                 pass
     87             p.wait();
     88 
     89             # Generate a list of whitelisted DSOs from the perf report.
     90             dso_list = []
     91             p = subprocess.Popen(perf_report_dso_args, stdout=subprocess.PIPE)
     92             for line in p.stdout:
     93                 # Skip comments.
     94                 if line.startswith('#'):
     95                     continue
     96                 # The output consists of percentage and DSO name.
     97                 tokens = line.split()
     98                 if len(tokens) < 2:
     99                     continue
    100                 # Store the DSO name if it appears in the whitelist.
    101                 dso_name = tokens[1]
    102                 for regex in self._DSO_WHITELIST_REGEX:
    103                     if regex.match(dso_name):
    104                         dso_list += [dso_name]
    105 
    106             p.wait();
    107 
    108             # Generate a mapping of DSOs to their build IDs.
    109             dso_to_build_ids = {}
    110             p = subprocess.Popen(perf_buildid_list_args, stdout=subprocess.PIPE)
    111             for line in p.stdout:
    112                 # The output consists of build ID and DSO name.
    113                 tokens = line.split()
    114                 if len(tokens) < 2:
    115                     continue
    116                 # The build ID list uses the full path of the DSOs, while the
    117                 # report output usesonly the basename. Store the basename to
    118                 # make lookups easier.
    119                 dso_to_build_ids[os.path.basename(tokens[1])] = tokens[0]
    120 
    121             p.wait();
    122 
    123 
    124         finally:
    125             # Delete the perf data file.
    126             try:
    127                 os.remove(perf_file_path)
    128             except OSError as e:
    129                 if e.errno != errno.ENONENT: raise
    130 
    131         if kernel_mapping is None:
    132             raise error.TestFail('Could not find kernel mapping in perf '
    133                                  'report.')
    134         # Get the kernel mapping values.
    135         kernel_mapping = kernel_mapping.split(':')[2]
    136         start, length, pgoff = re.sub(r'[][()@]', ' ',
    137                                       kernel_mapping).strip().split()
    138 
    139         # Check that all whitelisted DSOs from the report have build IDs.
    140         kernel_name = None
    141         kernel_build_id = None
    142         for dso in dso_list:
    143             if dso not in dso_to_build_ids:
    144                 raise error.TestFail('Could not find build ID for %s' % dso)
    145             if self._KERNEL_NAME_REGEX.match(dso):
    146                 kernel_name = dso
    147                 kernel_build_id = dso_to_build_ids[dso]
    148 
    149         # Make sure the kernel build ID was found.
    150         if not kernel_build_id:
    151             raise error.TestFail('Could not find kernel entry (containing '
    152                                  '"%s") in build ID list' % self._KERNEL_NAME)
    153 
    154         # Write keyvals.
    155         keyvals = {}
    156         keyvals['start'] = start
    157         keyvals['length'] = length
    158         keyvals['pgoff'] = pgoff
    159         keyvals['kernel_name'] = kernel_name
    160         keyvals['kernel_build_id'] = kernel_build_id
    161         self.write_perf_keyval(keyvals)
    162 
    163         # Make sure that the kernel mapping values follow an expected pattern,
    164         #
    165         # Expect one of two patterns:
    166         # (1) start == pgoff, e.g.:
    167         #   start=0x80008200
    168         #   pgoff=0x80008200
    169         #   len  =0xfffffff7ff7dff
    170         # (2) start < pgoff < start + len, e.g.:
    171         #   start=0x3bc00000
    172         #   pgoff=0xffffffffbcc00198
    173         #   len  =0xffffffff843fffff
    174         start = int(start, 0)
    175         length = int(length, 0)
    176         pgoff = int(pgoff, 0)
    177         if not (start == pgoff or start < pgoff < start + length):
    178             raise error.TestFail('Improper kernel mapping values!')
    179