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