xref: /aosp_15_r20/system/extras/pagecache/pagecache.py (revision 288bf5226967eb3dac5cce6c939ccc2a7f2b4fe5)
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