Home | History | Annotate | Download | only in frontends
      1 # Copyright 2014 The Chromium 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 
      5 """Command line frontend for Memory Inspector"""
      6 
      7 import json
      8 import memory_inspector
      9 import optparse
     10 import os
     11 import time
     12 
     13 from memory_inspector import constants
     14 from memory_inspector.classification import mmap_classifier
     15 from memory_inspector.core import backends
     16 from memory_inspector.data import serialization
     17 
     18 
     19 def main():
     20   COMMANDS = ['devices', 'ps', 'stats', 'mmaps', 'classified_mmaps']
     21   usage = ('%prog [options] ' + ' | '.join(COMMANDS))
     22   parser = optparse.OptionParser(usage=usage)
     23   parser.add_option('-b', '--backend', help='Backend name '
     24                     '(e.g., Android)', type='string', default='Android')
     25   parser.add_option('-s', '--device_id', help='Device '
     26                     'id (e.g., Android serial)', type='string')
     27   parser.add_option('-p', '--process_id', help='Target process id',
     28                     type='int')
     29   parser.add_option('-m', '--filter_process_name', help='Process '
     30                     'name to match', type='string')
     31   parser.add_option('-r', '--mmap_rule',
     32                     help='mmap rule', type='string',
     33                     default=os.path.join(constants.CLASSIFICATION_RULES_PATH,
     34                         'default', 'mmap-android.py'))
     35   (options, args) = parser.parse_args()
     36 
     37   memory_inspector.RegisterAllBackends()
     38 
     39   if not args or args[0] not in COMMANDS:
     40     parser.print_help()
     41     return -1
     42 
     43   if args[0] == 'devices':
     44     _ListDevices(options.backend)
     45     return 0
     46 
     47   number_of_devices = 0
     48   if options.device_id:
     49     device_id = options.device_id
     50     number_of_devices = 1
     51   else:
     52     for device in backends.ListDevices():
     53       if device.backend.name == options.backend:
     54         number_of_devices += 1
     55         device_id = device.id
     56 
     57   if number_of_devices == 0:
     58     print "No devices connected"
     59     return -1
     60 
     61   if number_of_devices > 1:
     62     print ('More than 1 device connected. You need to provide'
     63         ' --device_id')
     64     return -1
     65 
     66   device = backends.GetDevice(options.backend, device_id)
     67   if not device:
     68     print 'Device', device_id, 'does not exist'
     69     return -1
     70 
     71   device.Initialize()
     72   if args[0] == 'ps':
     73     if not options.filter_process_name:
     74       print 'Listing all processes'
     75     else:
     76       print ('Listing processes matching '
     77           + options.filter_process_name.lower())
     78     print ''
     79     print '%-10s : %-50s : %12s %12s %12s' % (
     80         'Process ID', 'Process Name', 'RUN_TIME', 'THREADS',
     81         'MEM_RSS_KB')
     82     print ''
     83     for process in device.ListProcesses():
     84       if (not options.filter_process_name or
     85           options.filter_process_name.lower() in process.name.lower()):
     86         stats = process.GetStats()
     87         run_time_min, run_time_sec = divmod(stats.run_time, 60)
     88         print '%10s : %-50s : %6s m %2s s %8s %12s' % (
     89             process.pid, _Truncate(process.name, 50), run_time_min,
     90             run_time_sec, stats.threads, stats.vm_rss)
     91     return 0
     92 
     93   if not options.process_id:
     94     print 'You need to provide --process_id'
     95     return -1
     96 
     97   process = device.GetProcess(options.process_id)
     98 
     99   if not process:
    100     print 'Cannot find process [%d] on device %s' % (
    101         options.process_id, device.id)
    102     return -1
    103   elif args[0] == 'stats':
    104     _ListProcessStats(process)
    105     return 0
    106   elif args[0] == 'mmaps':
    107     _ListProcessMmaps(process)
    108     return 0
    109   elif args[0] == 'classified_mmaps':
    110     _ListProcessClassifiedMmaps(process, options.mmap_rule)
    111     return 0
    112 
    113 
    114 def _ListDevices(backend_name):
    115   print 'Device list:'
    116   print ''
    117   for device in backends.ListDevices():
    118     if device.backend.name == backend_name:
    119       print '%-16s : %s' % (device.id, device.name)
    120 
    121 
    122 def _ListProcessStats(process):
    123   """Prints process stats periodically
    124   """
    125   print 'Stats for process: [%d] %s' % (process.pid, process.name)
    126   print '%-10s : %-50s : %12s %12s %13s %12s %14s' % (
    127       'Process ID', 'Process Name', 'RUN_TIME', 'THREADS',
    128       'CPU_USAGE', 'MEM_RSS_KB', 'PAGE_FAULTS')
    129   print ''
    130   while True:
    131     stats = process.GetStats()
    132     run_time_min, run_time_sec = divmod(stats.run_time, 60)
    133     print '%10s : %-50s : %6s m %2s s %8s %12s %13s %11s' % (
    134         process.pid, _Truncate(process.name, 50), run_time_min, run_time_sec,
    135         stats.threads, stats.cpu_usage, stats.vm_rss, stats.page_faults)
    136     time.sleep(1)
    137 
    138 
    139 def _ListProcessMmaps(process):
    140   """Prints process memory maps
    141   """
    142   print 'Memory Maps for process: [%d] %s' % (process.pid, process.name)
    143   print '%-10s %-10s %6s %12s %12s %13s %13s %-40s' % (
    144       'START', 'END', 'FLAGS', 'PRIV.DIRTY', 'PRIV.CLEAN',
    145       'SHARED DIRTY', 'SHARED CLEAN', 'MAPPED_FILE')
    146   print '%38s %12s %12s %13s' % ('(kb)', '(kb)', '(kb)', '(kb)')
    147   print ''
    148   maps = process.DumpMemoryMaps()
    149   for entry in maps.entries:
    150     print '%-10x %-10x %6s %12s %12s %13s %13s %-40s' % (
    151         entry.start, entry.end, entry.prot_flags,
    152         entry.priv_dirty_bytes / 1024, entry.priv_clean_bytes / 1024,
    153         entry.shared_dirty_bytes / 1024,
    154         entry.shared_clean_bytes / 1024, entry.mapped_file)
    155 
    156 
    157 def _ListProcessClassifiedMmaps(process, mmap_rule):
    158   """Prints process classified memory maps
    159   """
    160   maps = process.DumpMemoryMaps()
    161   if not os.path.exists(mmap_rule):
    162     print 'File', mmap_rule, 'not found'
    163     return
    164   with open(mmap_rule) as f:
    165     rules = mmap_classifier.LoadRules(f.read())
    166   classified_results_tree =  mmap_classifier.Classify(maps, rules)
    167   print json.dumps(classified_results_tree, cls=serialization.Encoder)
    168 
    169 
    170 def _Truncate(name, max_length):
    171   if len(name) <= max_length:
    172     return name
    173   return '%s...' % name[0:(max_length - 3)]
    174