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