1*288bf522SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*288bf522SAndroid Build Coastguard Worker 3*288bf522SAndroid Build Coastguard Workerimport curses 4*288bf522SAndroid Build Coastguard Workerimport operator 5*288bf522SAndroid Build Coastguard Workerimport optparse 6*288bf522SAndroid Build Coastguard Workerimport os 7*288bf522SAndroid Build Coastguard Workerimport re 8*288bf522SAndroid Build Coastguard Workerimport subprocess 9*288bf522SAndroid Build Coastguard Workerimport sys 10*288bf522SAndroid Build Coastguard Workerimport threading 11*288bf522SAndroid Build Coastguard Workerimport queue 12*288bf522SAndroid Build Coastguard Worker 13*288bf522SAndroid Build Coastguard WorkerSTATS_UPDATE_INTERVAL = 0.2 14*288bf522SAndroid Build Coastguard WorkerPAGE_SIZE = 4096 15*288bf522SAndroid Build Coastguard Worker 16*288bf522SAndroid Build Coastguard Workerclass PagecacheStats(): 17*288bf522SAndroid Build Coastguard Worker """Holds pagecache stats by accounting for pages added and removed. 18*288bf522SAndroid Build Coastguard Worker 19*288bf522SAndroid Build Coastguard Worker """ 20*288bf522SAndroid Build Coastguard Worker def __init__(self, inode_to_filename): 21*288bf522SAndroid Build Coastguard Worker self._inode_to_filename = inode_to_filename 22*288bf522SAndroid Build Coastguard Worker self._file_size = {} 23*288bf522SAndroid Build Coastguard Worker self._file_pages = {} 24*288bf522SAndroid Build Coastguard Worker self._total_pages_added = 0 25*288bf522SAndroid Build Coastguard Worker self._total_pages_removed = 0 26*288bf522SAndroid Build Coastguard Worker 27*288bf522SAndroid Build Coastguard Worker def add_page(self, device_number, inode, offset): 28*288bf522SAndroid Build Coastguard Worker # See if we can find the page in our lookup table 29*288bf522SAndroid Build Coastguard Worker if (device_number, inode) in self._inode_to_filename: 30*288bf522SAndroid Build Coastguard Worker filename, filesize = self._inode_to_filename[(device_number, inode)] 31*288bf522SAndroid Build Coastguard Worker if filename not in self._file_pages: 32*288bf522SAndroid Build Coastguard Worker self._file_pages[filename] = [1, 0] 33*288bf522SAndroid Build Coastguard Worker else: 34*288bf522SAndroid Build Coastguard Worker self._file_pages[filename][0] += 1 35*288bf522SAndroid Build Coastguard Worker 36*288bf522SAndroid Build Coastguard Worker self._total_pages_added += 1 37*288bf522SAndroid Build Coastguard Worker 38*288bf522SAndroid Build Coastguard Worker if filename not in self._file_size: 39*288bf522SAndroid Build Coastguard Worker self._file_size[filename] = filesize 40*288bf522SAndroid Build Coastguard Worker 41*288bf522SAndroid Build Coastguard Worker def remove_page(self, device_number, inode, offset): 42*288bf522SAndroid Build Coastguard Worker if (device_number, inode) in self._inode_to_filename: 43*288bf522SAndroid Build Coastguard Worker filename, filesize = self._inode_to_filename[(device_number, inode)] 44*288bf522SAndroid Build Coastguard Worker if filename not in self._file_pages: 45*288bf522SAndroid Build Coastguard Worker self._file_pages[filename] = [0, 1] 46*288bf522SAndroid Build Coastguard Worker else: 47*288bf522SAndroid Build Coastguard Worker self._file_pages[filename][1] += 1 48*288bf522SAndroid Build Coastguard Worker 49*288bf522SAndroid Build Coastguard Worker self._total_pages_removed += 1 50*288bf522SAndroid Build Coastguard Worker 51*288bf522SAndroid Build Coastguard Worker if filename not in self._file_size: 52*288bf522SAndroid Build Coastguard Worker self._file_size[filename] = filesize 53*288bf522SAndroid Build Coastguard Worker 54*288bf522SAndroid Build Coastguard Worker def pages_to_mb(self, num_pages): 55*288bf522SAndroid Build Coastguard Worker return "%.2f" % round(num_pages * PAGE_SIZE / 1024.0 / 1024.0, 2) 56*288bf522SAndroid Build Coastguard Worker 57*288bf522SAndroid Build Coastguard Worker def bytes_to_mb(self, num_bytes): 58*288bf522SAndroid Build Coastguard Worker return "%.2f" % round(int(num_bytes) / 1024.0 / 1024.0, 2) 59*288bf522SAndroid Build Coastguard Worker 60*288bf522SAndroid Build Coastguard Worker def print_pages_and_mb(self, num_pages): 61*288bf522SAndroid Build Coastguard Worker pages_string = str(num_pages) + ' (' + str(self.pages_to_mb(num_pages)) + ' MB)' 62*288bf522SAndroid Build Coastguard Worker return pages_string 63*288bf522SAndroid Build Coastguard Worker 64*288bf522SAndroid Build Coastguard Worker def reset_stats(self): 65*288bf522SAndroid Build Coastguard Worker self._file_pages.clear() 66*288bf522SAndroid Build Coastguard Worker self._total_pages_added = 0; 67*288bf522SAndroid Build Coastguard Worker self._total_pages_removed = 0; 68*288bf522SAndroid Build Coastguard Worker 69*288bf522SAndroid Build Coastguard Worker def print_stats(self): 70*288bf522SAndroid Build Coastguard Worker # Create new merged dict 71*288bf522SAndroid Build Coastguard Worker sorted_added = sorted(list(self._file_pages.items()), key=operator.itemgetter(1), reverse=True) 72*288bf522SAndroid Build Coastguard Worker row_format = "{:<70}{:<12}{:<14}{:<9}" 73*288bf522SAndroid Build Coastguard Worker print(row_format.format('NAME', 'ADDED (MB)', 'REMOVED (MB)', 'SIZE (MB)')) 74*288bf522SAndroid Build Coastguard Worker for filename, added in sorted_added: 75*288bf522SAndroid Build Coastguard Worker filesize = self._file_size[filename] 76*288bf522SAndroid Build Coastguard Worker added = self._file_pages[filename][0] 77*288bf522SAndroid Build Coastguard Worker removed = self._file_pages[filename][1] 78*288bf522SAndroid Build Coastguard Worker if (len(filename) > 64): 79*288bf522SAndroid Build Coastguard Worker filename = filename[-64:] 80*288bf522SAndroid Build Coastguard Worker print(row_format.format(filename, self.pages_to_mb(added), self.pages_to_mb(removed), self.bytes_to_mb(filesize))) 81*288bf522SAndroid Build Coastguard Worker 82*288bf522SAndroid Build Coastguard Worker print(row_format.format('TOTAL', self.pages_to_mb(self._total_pages_added), self.pages_to_mb(self._total_pages_removed), '')) 83*288bf522SAndroid Build Coastguard Worker 84*288bf522SAndroid Build Coastguard Worker def print_stats_curses(self, pad): 85*288bf522SAndroid Build Coastguard Worker sorted_added = sorted(list(self._file_pages.items()), key=operator.itemgetter(1), reverse=True) 86*288bf522SAndroid Build Coastguard Worker height, width = pad.getmaxyx() 87*288bf522SAndroid Build Coastguard Worker pad.clear() 88*288bf522SAndroid Build Coastguard Worker pad.addstr(0, 2, 'NAME'.ljust(68), curses.A_REVERSE) 89*288bf522SAndroid Build Coastguard Worker pad.addstr(0, 70, 'ADDED (MB)'.ljust(12), curses.A_REVERSE) 90*288bf522SAndroid Build Coastguard Worker pad.addstr(0, 82, 'REMOVED (MB)'.ljust(14), curses.A_REVERSE) 91*288bf522SAndroid Build Coastguard Worker pad.addstr(0, 96, 'SIZE (MB)'.ljust(9), curses.A_REVERSE) 92*288bf522SAndroid Build Coastguard Worker y = 1 93*288bf522SAndroid Build Coastguard Worker for filename, added_removed in sorted_added: 94*288bf522SAndroid Build Coastguard Worker filesize = self._file_size[filename] 95*288bf522SAndroid Build Coastguard Worker added = self._file_pages[filename][0] 96*288bf522SAndroid Build Coastguard Worker removed = self._file_pages[filename][1] 97*288bf522SAndroid Build Coastguard Worker if (len(filename) > 64): 98*288bf522SAndroid Build Coastguard Worker filename = filename[-64:] 99*288bf522SAndroid Build Coastguard Worker pad.addstr(y, 2, filename) 100*288bf522SAndroid Build Coastguard Worker pad.addstr(y, 70, self.pages_to_mb(added).rjust(10)) 101*288bf522SAndroid Build Coastguard Worker pad.addstr(y, 80, self.pages_to_mb(removed).rjust(14)) 102*288bf522SAndroid Build Coastguard Worker pad.addstr(y, 96, self.bytes_to_mb(filesize).rjust(9)) 103*288bf522SAndroid Build Coastguard Worker y += 1 104*288bf522SAndroid Build Coastguard Worker if y == height - 2: 105*288bf522SAndroid Build Coastguard Worker pad.addstr(y, 4, "<more...>") 106*288bf522SAndroid Build Coastguard Worker break 107*288bf522SAndroid Build Coastguard Worker y += 1 108*288bf522SAndroid Build Coastguard Worker pad.addstr(y, 2, 'TOTAL'.ljust(74), curses.A_REVERSE) 109*288bf522SAndroid Build Coastguard Worker pad.addstr(y, 70, str(self.pages_to_mb(self._total_pages_added)).rjust(10), curses.A_REVERSE) 110*288bf522SAndroid Build Coastguard Worker pad.addstr(y, 80, str(self.pages_to_mb(self._total_pages_removed)).rjust(14), curses.A_REVERSE) 111*288bf522SAndroid Build Coastguard Worker pad.refresh(0,0, 0,0, height,width) 112*288bf522SAndroid Build Coastguard Worker 113*288bf522SAndroid Build Coastguard Workerclass FileReaderThread(threading.Thread): 114*288bf522SAndroid Build Coastguard Worker """Reads data from a file/pipe on a worker thread. 115*288bf522SAndroid Build Coastguard Worker 116*288bf522SAndroid Build Coastguard Worker Use the standard threading. Thread object API to start and interact with the 117*288bf522SAndroid Build Coastguard Worker thread (start(), join(), etc.). 118*288bf522SAndroid Build Coastguard Worker """ 119*288bf522SAndroid Build Coastguard Worker 120*288bf522SAndroid Build Coastguard Worker def __init__(self, file_object, output_queue, text_file, chunk_size=-1): 121*288bf522SAndroid Build Coastguard Worker """Initializes a FileReaderThread. 122*288bf522SAndroid Build Coastguard Worker 123*288bf522SAndroid Build Coastguard Worker Args: 124*288bf522SAndroid Build Coastguard Worker file_object: The file or pipe to read from. 125*288bf522SAndroid Build Coastguard Worker output_queue: A queue.Queue object that will receive the data 126*288bf522SAndroid Build Coastguard Worker text_file: If True, the file will be read one line at a time, and 127*288bf522SAndroid Build Coastguard Worker chunk_size will be ignored. If False, line breaks are ignored and 128*288bf522SAndroid Build Coastguard Worker chunk_size must be set to a positive integer. 129*288bf522SAndroid Build Coastguard Worker chunk_size: When processing a non-text file (text_file = False), 130*288bf522SAndroid Build Coastguard Worker chunk_size is the amount of data to copy into the queue with each 131*288bf522SAndroid Build Coastguard Worker read operation. For text files, this parameter is ignored. 132*288bf522SAndroid Build Coastguard Worker """ 133*288bf522SAndroid Build Coastguard Worker threading.Thread.__init__(self) 134*288bf522SAndroid Build Coastguard Worker self._file_object = file_object 135*288bf522SAndroid Build Coastguard Worker self._output_queue = output_queue 136*288bf522SAndroid Build Coastguard Worker self._text_file = text_file 137*288bf522SAndroid Build Coastguard Worker self._chunk_size = chunk_size 138*288bf522SAndroid Build Coastguard Worker assert text_file or chunk_size > 0 139*288bf522SAndroid Build Coastguard Worker 140*288bf522SAndroid Build Coastguard Worker def run(self): 141*288bf522SAndroid Build Coastguard Worker """Overrides Thread's run() function. 142*288bf522SAndroid Build Coastguard Worker 143*288bf522SAndroid Build Coastguard Worker Returns when an EOF is encountered. 144*288bf522SAndroid Build Coastguard Worker """ 145*288bf522SAndroid Build Coastguard Worker if self._text_file: 146*288bf522SAndroid Build Coastguard Worker # Read a text file one line at a time. 147*288bf522SAndroid Build Coastguard Worker for line in self._file_object: 148*288bf522SAndroid Build Coastguard Worker self._output_queue.put(line) 149*288bf522SAndroid Build Coastguard Worker else: 150*288bf522SAndroid Build Coastguard Worker # Read binary or text data until we get to EOF. 151*288bf522SAndroid Build Coastguard Worker while True: 152*288bf522SAndroid Build Coastguard Worker chunk = self._file_object.read(self._chunk_size) 153*288bf522SAndroid Build Coastguard Worker if not chunk: 154*288bf522SAndroid Build Coastguard Worker break 155*288bf522SAndroid Build Coastguard Worker self._output_queue.put(chunk) 156*288bf522SAndroid Build Coastguard Worker 157*288bf522SAndroid Build Coastguard Worker def set_chunk_size(self, chunk_size): 158*288bf522SAndroid Build Coastguard Worker """Change the read chunk size. 159*288bf522SAndroid Build Coastguard Worker 160*288bf522SAndroid Build Coastguard Worker This function can only be called if the FileReaderThread object was 161*288bf522SAndroid Build Coastguard Worker created with an initial chunk_size > 0. 162*288bf522SAndroid Build Coastguard Worker Args: 163*288bf522SAndroid Build Coastguard Worker chunk_size: the new chunk size for this file. Must be > 0. 164*288bf522SAndroid Build Coastguard Worker """ 165*288bf522SAndroid Build Coastguard Worker # The chunk size can be changed asynchronously while a file is being read 166*288bf522SAndroid Build Coastguard Worker # in a worker thread. However, type of file can not be changed after the 167*288bf522SAndroid Build Coastguard Worker # the FileReaderThread has been created. These asserts verify that we are 168*288bf522SAndroid Build Coastguard Worker # only changing the chunk size, and not the type of file. 169*288bf522SAndroid Build Coastguard Worker assert not self._text_file 170*288bf522SAndroid Build Coastguard Worker assert chunk_size > 0 171*288bf522SAndroid Build Coastguard Worker self._chunk_size = chunk_size 172*288bf522SAndroid Build Coastguard Worker 173*288bf522SAndroid Build Coastguard Workerclass AdbUtils(): 174*288bf522SAndroid Build Coastguard Worker @staticmethod 175*288bf522SAndroid Build Coastguard Worker def add_adb_serial(adb_command, device_serial): 176*288bf522SAndroid Build Coastguard Worker if device_serial is not None: 177*288bf522SAndroid Build Coastguard Worker adb_command.insert(1, device_serial) 178*288bf522SAndroid Build Coastguard Worker adb_command.insert(1, '-s') 179*288bf522SAndroid Build Coastguard Worker 180*288bf522SAndroid Build Coastguard Worker @staticmethod 181*288bf522SAndroid Build Coastguard Worker def construct_adb_shell_command(shell_args, device_serial): 182*288bf522SAndroid Build Coastguard Worker adb_command = ['adb', 'shell', ' '.join(shell_args)] 183*288bf522SAndroid Build Coastguard Worker AdbUtils.add_adb_serial(adb_command, device_serial) 184*288bf522SAndroid Build Coastguard Worker return adb_command 185*288bf522SAndroid Build Coastguard Worker 186*288bf522SAndroid Build Coastguard Worker @staticmethod 187*288bf522SAndroid Build Coastguard Worker def run_adb_shell(shell_args, device_serial): 188*288bf522SAndroid Build Coastguard Worker """Runs "adb shell" with the given arguments. 189*288bf522SAndroid Build Coastguard Worker 190*288bf522SAndroid Build Coastguard Worker Args: 191*288bf522SAndroid Build Coastguard Worker shell_args: array of arguments to pass to adb shell. 192*288bf522SAndroid Build Coastguard Worker device_serial: if not empty, will add the appropriate command-line 193*288bf522SAndroid Build Coastguard Worker parameters so that adb targets the given device. 194*288bf522SAndroid Build Coastguard Worker Returns: 195*288bf522SAndroid Build Coastguard Worker A tuple containing the adb output (stdout & stderr) and the return code 196*288bf522SAndroid Build Coastguard Worker from adb. Will exit if adb fails to start. 197*288bf522SAndroid Build Coastguard Worker """ 198*288bf522SAndroid Build Coastguard Worker adb_command = AdbUtils.construct_adb_shell_command(shell_args, device_serial) 199*288bf522SAndroid Build Coastguard Worker 200*288bf522SAndroid Build Coastguard Worker adb_output = [] 201*288bf522SAndroid Build Coastguard Worker adb_return_code = 0 202*288bf522SAndroid Build Coastguard Worker try: 203*288bf522SAndroid Build Coastguard Worker adb_output = subprocess.check_output(adb_command, stderr=subprocess.STDOUT, 204*288bf522SAndroid Build Coastguard Worker shell=False, universal_newlines=True) 205*288bf522SAndroid Build Coastguard Worker except OSError as error: 206*288bf522SAndroid Build Coastguard Worker # This usually means that the adb executable was not found in the path. 207*288bf522SAndroid Build Coastguard Worker print('\nThe command "%s" failed with the following error:' 208*288bf522SAndroid Build Coastguard Worker % ' '.join(adb_command), file=sys.stderr) 209*288bf522SAndroid Build Coastguard Worker print(' %s' % str(error), file=sys.stderr) 210*288bf522SAndroid Build Coastguard Worker print('Is adb in your path?', file=sys.stderr) 211*288bf522SAndroid Build Coastguard Worker adb_return_code = error.errno 212*288bf522SAndroid Build Coastguard Worker adb_output = error 213*288bf522SAndroid Build Coastguard Worker except subprocess.CalledProcessError as error: 214*288bf522SAndroid Build Coastguard Worker # The process exited with an error. 215*288bf522SAndroid Build Coastguard Worker adb_return_code = error.returncode 216*288bf522SAndroid Build Coastguard Worker adb_output = error.output 217*288bf522SAndroid Build Coastguard Worker 218*288bf522SAndroid Build Coastguard Worker return (adb_output, adb_return_code) 219*288bf522SAndroid Build Coastguard Worker 220*288bf522SAndroid Build Coastguard Worker @staticmethod 221*288bf522SAndroid Build Coastguard Worker def do_preprocess_adb_cmd(command, serial): 222*288bf522SAndroid Build Coastguard Worker args = [command] 223*288bf522SAndroid Build Coastguard Worker dump, ret_code = AdbUtils.run_adb_shell(args, serial) 224*288bf522SAndroid Build Coastguard Worker if ret_code != 0: 225*288bf522SAndroid Build Coastguard Worker return None 226*288bf522SAndroid Build Coastguard Worker 227*288bf522SAndroid Build Coastguard Worker dump = ''.join(dump) 228*288bf522SAndroid Build Coastguard Worker return dump 229*288bf522SAndroid Build Coastguard Worker 230*288bf522SAndroid Build Coastguard Workerdef parse_atrace_line(line, pagecache_stats, app_name): 231*288bf522SAndroid Build Coastguard Worker # Find a mm_filemap_add_to_page_cache entry 232*288bf522SAndroid Build Coastguard Worker m = re.match('.* (mm_filemap_add_to_page_cache|mm_filemap_delete_from_page_cache): dev (\d+):(\d+) ino ([0-9a-z]+) page=([0-9a-z]+) pfn=([0-9a-z]+) ofs=(\d+).*', line) 233*288bf522SAndroid Build Coastguard Worker if m != None: 234*288bf522SAndroid Build Coastguard Worker # Get filename 235*288bf522SAndroid Build Coastguard Worker device_number = int(m.group(2)) << 8 | int(m.group(3)) 236*288bf522SAndroid Build Coastguard Worker if device_number == 0: 237*288bf522SAndroid Build Coastguard Worker return 238*288bf522SAndroid Build Coastguard Worker inode = int(m.group(4), 16) 239*288bf522SAndroid Build Coastguard Worker if app_name != None and not (app_name in m.group(0)): 240*288bf522SAndroid Build Coastguard Worker return 241*288bf522SAndroid Build Coastguard Worker if m.group(1) == 'mm_filemap_add_to_page_cache': 242*288bf522SAndroid Build Coastguard Worker pagecache_stats.add_page(device_number, inode, m.group(4)) 243*288bf522SAndroid Build Coastguard Worker elif m.group(1) == 'mm_filemap_delete_from_page_cache': 244*288bf522SAndroid Build Coastguard Worker pagecache_stats.remove_page(device_number, inode, m.group(4)) 245*288bf522SAndroid Build Coastguard Worker 246*288bf522SAndroid Build Coastguard Workerdef build_inode_lookup_table(inode_dump): 247*288bf522SAndroid Build Coastguard Worker inode2filename = {} 248*288bf522SAndroid Build Coastguard Worker text = inode_dump.splitlines() 249*288bf522SAndroid Build Coastguard Worker for line in text: 250*288bf522SAndroid Build Coastguard Worker result = re.match('([0-9]+)d? ([0-9]+) ([0-9]+) (.*)', line) 251*288bf522SAndroid Build Coastguard Worker if result: 252*288bf522SAndroid Build Coastguard Worker inode2filename[(int(result.group(1)), int(result.group(2)))] = (result.group(4), result.group(3)) 253*288bf522SAndroid Build Coastguard Worker 254*288bf522SAndroid Build Coastguard Worker return inode2filename; 255*288bf522SAndroid Build Coastguard Worker 256*288bf522SAndroid Build Coastguard Workerdef get_inode_data(datafile, dumpfile, adb_serial): 257*288bf522SAndroid Build Coastguard Worker if datafile is not None and os.path.isfile(datafile): 258*288bf522SAndroid Build Coastguard Worker print('Using cached inode data from ' + datafile) 259*288bf522SAndroid Build Coastguard Worker f = open(datafile, 'r') 260*288bf522SAndroid Build Coastguard Worker stat_dump = f.read(); 261*288bf522SAndroid Build Coastguard Worker else: 262*288bf522SAndroid Build Coastguard Worker # Build inode maps if we were tracing page cache 263*288bf522SAndroid Build Coastguard Worker print('Downloading inode data from device') 264*288bf522SAndroid Build Coastguard Worker stat_dump = AdbUtils.do_preprocess_adb_cmd( 265*288bf522SAndroid Build Coastguard Worker 'find /apex /system /system_ext /product /data /vendor ' + 266*288bf522SAndroid Build Coastguard Worker '-exec stat -c "%d %i %s %n" {} \;', adb_serial) 267*288bf522SAndroid Build Coastguard Worker if stat_dump is None: 268*288bf522SAndroid Build Coastguard Worker print('Could not retrieve inode data from device.') 269*288bf522SAndroid Build Coastguard Worker sys.exit(1) 270*288bf522SAndroid Build Coastguard Worker 271*288bf522SAndroid Build Coastguard Worker if dumpfile is not None: 272*288bf522SAndroid Build Coastguard Worker print('Storing inode data in ' + dumpfile) 273*288bf522SAndroid Build Coastguard Worker f = open(dumpfile, 'w') 274*288bf522SAndroid Build Coastguard Worker f.write(stat_dump) 275*288bf522SAndroid Build Coastguard Worker f.close() 276*288bf522SAndroid Build Coastguard Worker 277*288bf522SAndroid Build Coastguard Worker sys.stdout.write('Done.\n') 278*288bf522SAndroid Build Coastguard Worker 279*288bf522SAndroid Build Coastguard Worker return stat_dump 280*288bf522SAndroid Build Coastguard Worker 281*288bf522SAndroid Build Coastguard Workerdef read_and_parse_trace_file(trace_file, pagecache_stats, app_name): 282*288bf522SAndroid Build Coastguard Worker for line in trace_file: 283*288bf522SAndroid Build Coastguard Worker parse_atrace_line(line, pagecache_stats, app_name) 284*288bf522SAndroid Build Coastguard Worker pagecache_stats.print_stats(); 285*288bf522SAndroid Build Coastguard Worker 286*288bf522SAndroid Build Coastguard Workerdef read_and_parse_trace_data_live(stdout, stderr, pagecache_stats, app_name): 287*288bf522SAndroid Build Coastguard Worker # Start reading trace data 288*288bf522SAndroid Build Coastguard Worker stdout_queue = queue.Queue(maxsize=128) 289*288bf522SAndroid Build Coastguard Worker stderr_queue = queue.Queue() 290*288bf522SAndroid Build Coastguard Worker 291*288bf522SAndroid Build Coastguard Worker stdout_thread = FileReaderThread(stdout, stdout_queue, 292*288bf522SAndroid Build Coastguard Worker text_file=True, chunk_size=64) 293*288bf522SAndroid Build Coastguard Worker stderr_thread = FileReaderThread(stderr, stderr_queue, 294*288bf522SAndroid Build Coastguard Worker text_file=True) 295*288bf522SAndroid Build Coastguard Worker stdout_thread.start() 296*288bf522SAndroid Build Coastguard Worker stderr_thread.start() 297*288bf522SAndroid Build Coastguard Worker 298*288bf522SAndroid Build Coastguard Worker stdscr = curses.initscr() 299*288bf522SAndroid Build Coastguard Worker 300*288bf522SAndroid Build Coastguard Worker try: 301*288bf522SAndroid Build Coastguard Worker height, width = stdscr.getmaxyx() 302*288bf522SAndroid Build Coastguard Worker curses.noecho() 303*288bf522SAndroid Build Coastguard Worker curses.cbreak() 304*288bf522SAndroid Build Coastguard Worker stdscr.keypad(True) 305*288bf522SAndroid Build Coastguard Worker stdscr.nodelay(True) 306*288bf522SAndroid Build Coastguard Worker stdscr.refresh() 307*288bf522SAndroid Build Coastguard Worker # We need at least a 30x100 window 308*288bf522SAndroid Build Coastguard Worker used_width = max(width, 100) 309*288bf522SAndroid Build Coastguard Worker used_height = max(height, 30) 310*288bf522SAndroid Build Coastguard Worker 311*288bf522SAndroid Build Coastguard Worker # Create a pad for pagecache stats 312*288bf522SAndroid Build Coastguard Worker pagecache_pad = curses.newpad(used_height - 2, used_width) 313*288bf522SAndroid Build Coastguard Worker 314*288bf522SAndroid Build Coastguard Worker stdscr.addstr(used_height - 1, 0, 'KEY SHORTCUTS: (r)eset stats, CTRL-c to quit') 315*288bf522SAndroid Build Coastguard Worker while (stdout_thread.isAlive() or stderr_thread.isAlive() or 316*288bf522SAndroid Build Coastguard Worker not stdout_queue.empty() or not stderr_queue.empty()): 317*288bf522SAndroid Build Coastguard Worker while not stderr_queue.empty(): 318*288bf522SAndroid Build Coastguard Worker # Pass along errors from adb. 319*288bf522SAndroid Build Coastguard Worker line = stderr_queue.get().decode("utf-8") 320*288bf522SAndroid Build Coastguard Worker sys.stderr.write(line) 321*288bf522SAndroid Build Coastguard Worker while True: 322*288bf522SAndroid Build Coastguard Worker try: 323*288bf522SAndroid Build Coastguard Worker line = stdout_queue.get(True, STATS_UPDATE_INTERVAL).decode("utf-8") 324*288bf522SAndroid Build Coastguard Worker parse_atrace_line(line, pagecache_stats, app_name) 325*288bf522SAndroid Build Coastguard Worker except (queue.Empty, KeyboardInterrupt): 326*288bf522SAndroid Build Coastguard Worker break 327*288bf522SAndroid Build Coastguard Worker 328*288bf522SAndroid Build Coastguard Worker key = '' 329*288bf522SAndroid Build Coastguard Worker try: 330*288bf522SAndroid Build Coastguard Worker key = stdscr.getkey() 331*288bf522SAndroid Build Coastguard Worker except: 332*288bf522SAndroid Build Coastguard Worker pass 333*288bf522SAndroid Build Coastguard Worker 334*288bf522SAndroid Build Coastguard Worker if key == 'r': 335*288bf522SAndroid Build Coastguard Worker pagecache_stats.reset_stats() 336*288bf522SAndroid Build Coastguard Worker 337*288bf522SAndroid Build Coastguard Worker pagecache_stats.print_stats_curses(pagecache_pad) 338*288bf522SAndroid Build Coastguard Worker except Exception as e: 339*288bf522SAndroid Build Coastguard Worker curses.endwin() 340*288bf522SAndroid Build Coastguard Worker print(e) 341*288bf522SAndroid Build Coastguard Worker finally: 342*288bf522SAndroid Build Coastguard Worker curses.endwin() 343*288bf522SAndroid Build Coastguard Worker # The threads should already have stopped, so this is just for cleanup. 344*288bf522SAndroid Build Coastguard Worker stdout_thread.join() 345*288bf522SAndroid Build Coastguard Worker stderr_thread.join() 346*288bf522SAndroid Build Coastguard Worker 347*288bf522SAndroid Build Coastguard Worker stdout.close() 348*288bf522SAndroid Build Coastguard Worker stderr.close() 349*288bf522SAndroid Build Coastguard Worker 350*288bf522SAndroid Build Coastguard Workerdef parse_options(argv): 351*288bf522SAndroid Build Coastguard Worker usage = 'Usage: %prog [options]' 352*288bf522SAndroid Build Coastguard Worker desc = 'Example: %prog' 353*288bf522SAndroid Build Coastguard Worker parser = optparse.OptionParser(usage=usage, description=desc) 354*288bf522SAndroid Build Coastguard Worker parser.add_option('-d', dest='inode_dump_file', metavar='FILE', 355*288bf522SAndroid Build Coastguard Worker help='Dump the inode data read from a device to a file.' 356*288bf522SAndroid Build Coastguard Worker ' This file can then be reused with the -i option to speed' 357*288bf522SAndroid Build Coastguard Worker ' up future invocations of this script.') 358*288bf522SAndroid Build Coastguard Worker parser.add_option('-i', dest='inode_data_file', metavar='FILE', 359*288bf522SAndroid Build Coastguard Worker help='Read cached inode data from a file saved arlier with the' 360*288bf522SAndroid Build Coastguard Worker ' -d option.') 361*288bf522SAndroid Build Coastguard Worker parser.add_option('-s', '--serial', dest='device_serial', type='string', 362*288bf522SAndroid Build Coastguard Worker help='adb device serial number') 363*288bf522SAndroid Build Coastguard Worker parser.add_option('-f', dest='trace_file', metavar='FILE', 364*288bf522SAndroid Build Coastguard Worker help='Show stats from a trace file, instead of running live.') 365*288bf522SAndroid Build Coastguard Worker parser.add_option('-a', dest='app_name', type='string', 366*288bf522SAndroid Build Coastguard Worker help='filter a particular app') 367*288bf522SAndroid Build Coastguard Worker 368*288bf522SAndroid Build Coastguard Worker options, categories = parser.parse_args(argv[1:]) 369*288bf522SAndroid Build Coastguard Worker if options.inode_dump_file and options.inode_data_file: 370*288bf522SAndroid Build Coastguard Worker parser.error('options -d and -i can\'t be used at the same time') 371*288bf522SAndroid Build Coastguard Worker return (options, categories) 372*288bf522SAndroid Build Coastguard Worker 373*288bf522SAndroid Build Coastguard Workerdef main(): 374*288bf522SAndroid Build Coastguard Worker options, categories = parse_options(sys.argv) 375*288bf522SAndroid Build Coastguard Worker 376*288bf522SAndroid Build Coastguard Worker # Load inode data for this device 377*288bf522SAndroid Build Coastguard Worker inode_data = get_inode_data(options.inode_data_file, options.inode_dump_file, 378*288bf522SAndroid Build Coastguard Worker options.device_serial) 379*288bf522SAndroid Build Coastguard Worker # Build (dev, inode) -> filename hash 380*288bf522SAndroid Build Coastguard Worker inode_lookup_table = build_inode_lookup_table(inode_data) 381*288bf522SAndroid Build Coastguard Worker # Init pagecache stats 382*288bf522SAndroid Build Coastguard Worker pagecache_stats = PagecacheStats(inode_lookup_table) 383*288bf522SAndroid Build Coastguard Worker 384*288bf522SAndroid Build Coastguard Worker if options.trace_file is not None: 385*288bf522SAndroid Build Coastguard Worker if not os.path.isfile(options.trace_file): 386*288bf522SAndroid Build Coastguard Worker print('Couldn\'t load trace file.', file=sys.stderr) 387*288bf522SAndroid Build Coastguard Worker sys.exit(1) 388*288bf522SAndroid Build Coastguard Worker trace_file = open(options.trace_file, 'r') 389*288bf522SAndroid Build Coastguard Worker read_and_parse_trace_file(trace_file, pagecache_stats, options.app_name) 390*288bf522SAndroid Build Coastguard Worker else: 391*288bf522SAndroid Build Coastguard Worker # Construct and execute trace command 392*288bf522SAndroid Build Coastguard Worker trace_cmd = AdbUtils.construct_adb_shell_command(['atrace', '--stream', 'pagecache'], 393*288bf522SAndroid Build Coastguard Worker options.device_serial) 394*288bf522SAndroid Build Coastguard Worker 395*288bf522SAndroid Build Coastguard Worker try: 396*288bf522SAndroid Build Coastguard Worker atrace = subprocess.Popen(trace_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, 397*288bf522SAndroid Build Coastguard Worker stderr=subprocess.PIPE) 398*288bf522SAndroid Build Coastguard Worker except OSError as error: 399*288bf522SAndroid Build Coastguard Worker print('The command failed', file=sys.stderr) 400*288bf522SAndroid Build Coastguard Worker sys.exit(1) 401*288bf522SAndroid Build Coastguard Worker 402*288bf522SAndroid Build Coastguard Worker read_and_parse_trace_data_live(atrace.stdout, atrace.stderr, pagecache_stats, options.app_name) 403*288bf522SAndroid Build Coastguard Worker 404*288bf522SAndroid Build Coastguard Workerif __name__ == "__main__": 405*288bf522SAndroid Build Coastguard Worker main() 406