3 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
17 from string import Template
19 sys.path.append(os.path.join(sys.path[0], os.pardir, os.pardir, os.pardir,
21 from pylib import android_commands
22 from pylib import constants
26 ('Total', '.* r... '),
27 ('Read-only', '.* r--. '),
28 ('Read-write', '.* rw.. '),
29 ('Executable', '.* ..x. '),
30 ('Anonymous total', '.* ""'),
31 ('Anonymous read-write', '.* rw.. .* ""'),
32 ('Anonymous executable (JIT\'ed code)', '.* ..x. .* ""'),
33 ('File total', '.* .... .* "/.*"'),
34 ('File read-write', '.* rw.. .* "/.*"'),
35 ('File executable', '.* ..x. .* "/.*"'),
36 ('/dev files', '.* r... .* "/dev/.*"'),
37 ('Dalvik', '.* rw.. .* "/.*dalvik.*"'),
38 ('Dalvik heap', '.* rw.. .* "/.*dalvik-heap.*"'),
39 ('Native heap (malloc)', '.* r... .* ".*malloc.*"'),
40 ('Ashmem', '.* rw.. .* "/dev/ashmem '),
41 ('Native library total', '.* r... .* "/data/app-lib/'),
42 ('Native library read-only', '.* r--. .* "/data/app-lib/'),
43 ('Native library read-write', '.* rw-. .* "/data/app-lib/'),
44 ('Native library executable', '.* r.x. .* "/data/app-lib/'),
48 def _CollectMemoryStats(memdump, region_filters):
50 mem_usage_for_regions = None
52 for region_filter in region_filters:
53 regexps[region_filter] = re.compile(region_filter)
56 mem_usage_for_regions = {}
57 processes.append(mem_usage_for_regions)
59 matched_regions = Set([])
60 for region_filter in region_filters:
61 if regexps[region_filter].match(line.rstrip('\r\n')):
62 matched_regions.add(region_filter)
63 if not region_filter in mem_usage_for_regions:
64 mem_usage_for_regions[region_filter] = {
65 'private_unevictable': 0,
68 'shared_app_unevictable': 0.0,
69 'shared_other_unevictable': 0,
72 for matched_region in matched_regions:
73 mem_usage = mem_usage_for_regions[matched_region]
75 for token in line.split(' '):
76 if (key + '=') in token:
77 field = token.split('=')[1]
78 if key != 'shared_app':
79 mem_usage[key] += int(field)
80 else: # shared_app=[\d:\d,\d:\d...]
81 array = field[1:-1].split(',')
82 for i in xrange(len(array)):
83 shared_app, shared_app_unevictable = array[i].split(':')
84 mem_usage['shared_app'] += float(shared_app) / (i + 2)
85 mem_usage['shared_app_unevictable'] += \
86 float(shared_app_unevictable) / (i + 2)
91 def _ConvertMemoryField(field):
92 return str(field / (1024.0 * 1024))
95 def _DumpCSV(processes_stats):
98 for process in processes_stats:
100 print (',Process ' + str(i) + ',private,private_unevictable,shared_app,' +
101 'shared_app_unevictable,shared_other,shared_other_unevictable,')
102 for (k, v) in _ENTRIES:
104 print ',' + k + ',0,0,0,0,0,'
106 if not v in total_map:
107 total_map[v] = {'resident':0, 'unevictable':0}
108 total_map[v]['resident'] += (process[v]['private'] +
109 process[v]['shared_app'])
110 total_map[v]['unevictable'] += process[v]['private_unevictable'] + \
111 process[v]['shared_app_unevictable']
114 _ConvertMemoryField(process[v]['private']) + ',' +
115 _ConvertMemoryField(process[v]['private_unevictable']) + ',' +
116 _ConvertMemoryField(process[v]['shared_app']) + ',' +
117 _ConvertMemoryField(process[v]['shared_app_unevictable']) + ',' +
118 _ConvertMemoryField(process[v]['shared_other']) + ',' +
119 _ConvertMemoryField(process[v]['shared_other_unevictable']) + ','
123 for (k, v) in _ENTRIES:
124 if not v in total_map:
125 print ',' + k + ',0,0,'
127 print (',' + k + ',' + _ConvertMemoryField(total_map[v]['resident']) + ',' +
128 _ConvertMemoryField(total_map[v]['unevictable']) + ',')
132 def _RunManualGraph(package_name, interval):
133 _AREA_TYPES = ('private', 'private_unevictable', 'shared_app',
134 'shared_app_unevictable', 'shared_other',
135 'shared_other_unevictable')
137 legends = ['Seconds'] + [entry + '_' + area
138 for entry, _ in _ENTRIES
139 for area in _AREA_TYPES]
140 should_quit = threading.Event()
142 def _GenerateGraph():
146 <script type='text/javascript' src='https://www.google.com/jsapi'></script>
147 <script type='text/javascript'>
148 google.load('visualization', '1', {packages:['corechart', 'table']});
149 google.setOnLoadCallback(createPidSelector);
150 var pids = $JSON_PIDS;
151 var pids_info = $JSON_PIDS_INFO;
152 function drawVisualization(pid) {
153 var data = google.visualization.arrayToDataTable(
158 title: 'Memory Report (KB) for ' + pid,
159 vAxis: {title: 'Time', titleTextStyle: {color: 'red'}},
163 var chart = new google.visualization.BarChart(
164 document.getElementById('chart_div'));
165 chart.draw(data, charOptions);
167 var table = new google.visualization.Table(
168 document.getElementById('table_div'));
172 function createPidSelector() {
173 var pid_selector = document.getElementById('pid_selector');
175 var option = document.createElement('option');
176 option.text = option.value = pids[pid];
177 pid_selector.appendChild(option);
179 pid_selector.addEventListener('change',
181 drawVisualization(this.selectedOptions[0].value);
184 drawVisualization(pids[0]);
189 PIDS: <select id='pid_selector'></select>
190 <div id='chart_div' style="width: 1024px; height: 800px;"></div>
191 <div id='table_div' style="width: 1024px; height: 640px;"></div>
195 pids = sorted(all_pids.keys())
196 pids_info = dict(zip(pids,
198 all_pids[p] for p in pids
200 print Template(_HTML_TEMPLATE).safe_substitute({
201 'JSON_PIDS': json.dumps(pids),
202 'JSON_PIDS_INFO': json.dumps(pids_info)
207 def _CollectStats(count):
208 adb = android_commands.AndroidCommands()
209 pid_list = adb.ExtractPid(package_name)
210 memdump = adb.RunShellCommand('/data/local/tmp/memdump ' +
212 process_stats = _CollectMemoryStats(memdump,
213 [value for (key, value) in _ENTRIES])
214 for (pid, process) in zip(pid_list, process_stats):
215 first_pid_entry = True
216 for (k, v) in _ENTRIES:
219 for area_type in _AREA_TYPES:
220 legend = k + '_' + area_type
221 if pid not in all_pids:
224 all_pids[pid].append(['%ds' % (count * interval)] +
225 [0] * (len(legends) - 1))
226 first_pid_entry = False
227 mem_kb = process[v][area_type] / 1024
228 all_pids[pid][-1][legends.index(legend)] = mem_kb
232 while not should_quit.is_set():
233 print >>sys.stderr, 'Collecting ', count
236 should_quit.wait(interval)
238 t = threading.Thread(target=_Loop)
241 print >>sys.stderr, 'Press enter or CTRL+C to stop'
245 except KeyboardInterrupt:
256 parser = optparse.OptionParser(usage='Usage: %prog [options]',
258 parser.add_option('-m',
261 help='Manually collect data and generate a graph.')
262 parser.add_option('-p',
264 default=constants.PACKAGE_INFO['chrome'].package,
265 help='Package name to collect.')
266 parser.add_option('-i',
270 help='Interval in seconds for manual collections.')
271 options, args = parser.parse_args(argv)
272 if options.manual_graph:
273 return _RunManualGraph(options.package, options.interval)
274 _DumpCSV(_CollectMemoryStats(sys.stdin, [value for (key, value) in _ENTRIES]))
277 if __name__ == '__main__':