1e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch# Copyright 2014 The Chromium Authors. All rights reserved.
2e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch# Use of this source code is governed by a BSD-style license that can be
3e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch# found in the LICENSE file.
4e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
5e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch"""Command line frontend for Memory Inspector"""
6e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
70529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochimport json
8e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdochimport memory_inspector
9e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdochimport optparse
100529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochimport os
11e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdochimport time
12e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
13cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)from memory_inspector import constants
140529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochfrom memory_inspector.classification import mmap_classifier
15e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdochfrom memory_inspector.core import backends
160529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochfrom memory_inspector.data import serialization
17e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
18e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
190529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochdef main():
200529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  COMMANDS = ['devices', 'ps', 'stats', 'mmaps', 'classified_mmaps']
210529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  usage = ('%prog [options] ' + ' | '.join(COMMANDS))
22e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  parser = optparse.OptionParser(usage=usage)
23e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  parser.add_option('-b', '--backend', help='Backend name '
24e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch                    '(e.g., Android)', type='string', default='Android')
25e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  parser.add_option('-s', '--device_id', help='Device '
26a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch                    'id (e.g., Android serial)', type='string')
27e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  parser.add_option('-p', '--process_id', help='Target process id',
28a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch                    type='int')
29e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  parser.add_option('-m', '--filter_process_name', help='Process '
30e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch                    'name to match', type='string')
310529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  parser.add_option('-r', '--mmap_rule',
320529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                    help='mmap rule', type='string',
33cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                    default=os.path.join(constants.CLASSIFICATION_RULES_PATH,
34cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                        'default', 'mmap-android.py'))
35e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  (options, args) = parser.parse_args()
36e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
37e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  memory_inspector.RegisterAllBackends()
38e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
390529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  if not args or args[0] not in COMMANDS:
40e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch    parser.print_help()
41e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch    return -1
420529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
43e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  if args[0] == 'devices':
44e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch    _ListDevices(options.backend)
45a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    return 0
46a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
47a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  number_of_devices = 0
48a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  if options.device_id:
49a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    device_id = options.device_id
50a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    number_of_devices = 1
51a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  else:
52a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    for device in backends.ListDevices():
53a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch      if device.backend.name == options.backend:
54a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch        number_of_devices += 1
55a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch        device_id = device.id
56a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
57a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  if number_of_devices == 0:
58a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    print "No devices connected"
59a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    return -1
60a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
61a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  if number_of_devices > 1:
62a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    print ('More than 1 device connected. You need to provide'
63a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch        ' --device_id')
64a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    return -1
65a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
660529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  device = backends.GetDevice(options.backend, device_id)
670529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  if not device:
680529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    print 'Device', device_id, 'does not exist'
690529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    return -1
70a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
710529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  device.Initialize()
720529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  if args[0] == 'ps':
730529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    if not options.filter_process_name:
740529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      print 'Listing all processes'
75e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch    else:
760529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      print ('Listing processes matching '
770529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch          + options.filter_process_name.lower())
780529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    print ''
790529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    print '%-10s : %-50s : %12s %12s %12s' % (
800529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        'Process ID', 'Process Name', 'RUN_TIME', 'THREADS',
810529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        'MEM_RSS_KB')
820529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    print ''
830529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    for process in device.ListProcesses():
840529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      if (not options.filter_process_name or
850529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch          options.filter_process_name.lower() in process.name.lower()):
860529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        stats = process.GetStats()
870529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        run_time_min, run_time_sec = divmod(stats.run_time, 60)
880529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        print '%10s : %-50s : %6s m %2s s %8s %12s' % (
891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            process.pid, _Truncate(process.name, 50), run_time_min,
901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            run_time_sec, stats.threads, stats.vm_rss)
91a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    return 0
920529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
930529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  if not options.process_id:
940529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    print 'You need to provide --process_id'
95e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch    return -1
96e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
970529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  process = device.GetProcess(options.process_id)
980529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
990529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  if not process:
1000529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    print 'Cannot find process [%d] on device %s' % (
1010529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        options.process_id, device.id)
1020529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    return -1
1030529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  elif args[0] == 'stats':
1040529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    _ListProcessStats(process)
1050529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    return 0
1060529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  elif args[0] == 'mmaps':
1070529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    _ListProcessMmaps(process)
1080529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    return 0
1090529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  elif args[0] == 'classified_mmaps':
1100529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    _ListProcessClassifiedMmaps(process, options.mmap_rule)
1110529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    return 0
1120529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
113e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
114e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdochdef _ListDevices(backend_name):
115e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  print 'Device list:'
116e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  print ''
117e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  for device in backends.ListDevices():
118e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch    if device.backend.name == backend_name:
119e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch      print '%-16s : %s' % (device.id, device.name)
120e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
121e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
1220529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochdef _ListProcessStats(process):
1230529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  """Prints process stats periodically
124e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  """
1250529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  print 'Stats for process: [%d] %s' % (process.pid, process.name)
1260529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  print '%-10s : %-50s : %12s %12s %13s %12s %14s' % (
1270529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      'Process ID', 'Process Name', 'RUN_TIME', 'THREADS',
1280529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      'CPU_USAGE', 'MEM_RSS_KB', 'PAGE_FAULTS')
129a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  print ''
1300529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  while True:
1310529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    stats = process.GetStats()
1320529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    run_time_min, run_time_sec = divmod(stats.run_time, 60)
1330529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    print '%10s : %-50s : %6s m %2s s %8s %12s %13s %11s' % (
1341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        process.pid, _Truncate(process.name, 50), run_time_min, run_time_sec,
1350529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        stats.threads, stats.cpu_usage, stats.vm_rss, stats.page_faults)
1360529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    time.sleep(1)
137a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
138a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
1390529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochdef _ListProcessMmaps(process):
1400529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  """Prints process memory maps
1410529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  """
1420529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  print 'Memory Maps for process: [%d] %s' % (process.pid, process.name)
1430529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  print '%-10s %-10s %6s %12s %12s %13s %13s %-40s' % (
1440529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      'START', 'END', 'FLAGS', 'PRIV.DIRTY', 'PRIV.CLEAN',
1450529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      'SHARED DIRTY', 'SHARED CLEAN', 'MAPPED_FILE')
1460529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  print '%38s %12s %12s %13s' % ('(kb)', '(kb)', '(kb)', '(kb)')
147e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  print ''
1480529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  maps = process.DumpMemoryMaps()
1490529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  for entry in maps.entries:
1500529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    print '%-10x %-10x %6s %12s %12s %13s %13s %-40s' % (
1510529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        entry.start, entry.end, entry.prot_flags,
1520529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        entry.priv_dirty_bytes / 1024, entry.priv_clean_bytes / 1024,
1530529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        entry.shared_dirty_bytes / 1024,
1540529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch        entry.shared_clean_bytes / 1024, entry.mapped_file)
155e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
156e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
1570529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochdef _ListProcessClassifiedMmaps(process, mmap_rule):
1580529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  """Prints process classified memory maps
1590529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  """
1600529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  maps = process.DumpMemoryMaps()
1610529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  if not os.path.exists(mmap_rule):
1620529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    print 'File', mmap_rule, 'not found'
1630529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    return
1640529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  with open(mmap_rule) as f:
1650529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    rules = mmap_classifier.LoadRules(f.read())
1660529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  classified_results_tree =  mmap_classifier.Classify(maps, rules)
1670529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  print json.dumps(classified_results_tree, cls=serialization.Encoder)
168e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
169e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch
1701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccidef _Truncate(name, max_length):
1711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if len(name) <= max_length:
1721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return name
1731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  return '%s...' % name[0:(max_length - 3)]
174