command_line.py revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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 7import json 8import memory_inspector 9import optparse 10import os 11import time 12 13from memory_inspector import constants 14from memory_inspector.classification import mmap_classifier 15from memory_inspector.core import backends 16from memory_inspector.data import serialization 17 18 19def 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 114def _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 122def _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 139def _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 157def _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 170def _Truncate(name, max_length): 171 if len(name) <= max_length: 172 return name 173 return '%s...' % name[0:(max_length - 3)] 174