Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / tools / android / memdump / memreport.py
1 #!/usr/bin/env python
2 #
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.
6
7 import collections
8 import json
9 import optparse
10 import os
11 import re
12 import threading
13 import time
14 import sys
15
16 from sets import Set
17 from string import Template
18
19 sys.path.append(os.path.join(sys.path[0], os.pardir, os.pardir, os.pardir,
20                              'build','android'))
21 from pylib import android_commands
22 from pylib import constants
23
24
25 _ENTRIES = [
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/'),
45 ]
46
47
48 def _CollectMemoryStats(memdump, region_filters):
49   processes = []
50   mem_usage_for_regions = None
51   regexps = {}
52   for region_filter in region_filters:
53     regexps[region_filter] = re.compile(region_filter)
54   for line in memdump:
55     if 'PID=' in line:
56       mem_usage_for_regions = {}
57       processes.append(mem_usage_for_regions)
58       continue
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,
66               'private': 0,
67               'shared_app': 0.0,
68               'shared_app_unevictable': 0.0,
69               'shared_other_unevictable': 0,
70               'shared_other': 0,
71           }
72     for matched_region in matched_regions:
73       mem_usage = mem_usage_for_regions[matched_region]
74       for key in mem_usage:
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)
87             break
88   return processes
89
90
91 def _ConvertMemoryField(field):
92   return str(field / (1024.0 * 1024))
93
94
95 def _DumpCSV(processes_stats):
96   total_map = {}
97   i = 0
98   for process in processes_stats:
99     i += 1
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:
103       if not v in process:
104         print ',' + k + ',0,0,0,0,0,'
105         continue
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']
112       print (
113           ',' + k + ',' +
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']) + ','
120           )
121     print ''
122
123   for (k, v) in _ENTRIES:
124     if not v in total_map:
125       print ',' + k + ',0,0,'
126       continue
127     print (',' + k + ',' + _ConvertMemoryField(total_map[v]['resident']) + ',' +
128            _ConvertMemoryField(total_map[v]['unevictable']) + ',')
129   print ''
130
131
132 def _RunManualGraph(package_name, interval):
133   _AREA_TYPES = ('private', 'private_unevictable', 'shared_app',
134                  'shared_app_unevictable', 'shared_other',
135                  'shared_other_unevictable')
136   all_pids = {}
137   legends = ['Seconds'] + [entry + '_' + area
138                            for entry, _ in _ENTRIES
139                            for area in _AREA_TYPES]
140   should_quit = threading.Event()
141
142   def _GenerateGraph():
143     _HTML_TEMPLATE = """
144 <html>
145   <head>
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(
154           pids_info[pid]
155         );
156
157         var charOptions = {
158           title: 'Memory Report (KB) for ' + pid,
159           vAxis: {title: 'Time',  titleTextStyle: {color: 'red'}},
160           isStacked : true
161         };
162
163         var chart = new google.visualization.BarChart(
164             document.getElementById('chart_div'));
165         chart.draw(data, charOptions);
166
167         var table = new google.visualization.Table(
168             document.getElementById('table_div'));
169         table.draw(data);
170       }
171
172       function createPidSelector() {
173         var pid_selector = document.getElementById('pid_selector');
174         for (pid in pids) {
175           var option = document.createElement('option');
176           option.text = option.value = pids[pid];
177           pid_selector.appendChild(option);
178         }
179         pid_selector.addEventListener('change',
180           function() {
181             drawVisualization(this.selectedOptions[0].value);
182           }
183         );
184         drawVisualization(pids[0]);
185       }
186     </script>
187   </head>
188   <body>
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>
192   </body>
193 </html>
194 """
195     pids = sorted(all_pids.keys())
196     pids_info = dict(zip(pids,
197                          [ [legends] +
198                            all_pids[p] for p in pids
199                          ]))
200     print Template(_HTML_TEMPLATE).safe_substitute({
201         'JSON_PIDS': json.dumps(pids),
202         'JSON_PIDS_INFO': json.dumps(pids_info)
203     })
204
205
206
207   def _CollectStats(count):
208     adb = android_commands.AndroidCommands()
209     pid_list = adb.ExtractPid(package_name)
210     memdump = adb.RunShellCommand('/data/local/tmp/memdump ' +
211                                   ' '.join(pid_list))
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:
217         if v not in process:
218           continue
219         for area_type in _AREA_TYPES:
220           legend = k + '_' + area_type
221           if pid not in all_pids:
222             all_pids[pid] = []
223           if first_pid_entry:
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
229
230   def _Loop():
231     count = 0
232     while not should_quit.is_set():
233       print >>sys.stderr, 'Collecting ', count
234       _CollectStats(count)
235       count += 1
236       should_quit.wait(interval)
237
238   t = threading.Thread(target=_Loop)
239
240
241   print >>sys.stderr, 'Press enter or CTRL+C to stop'
242   t.start()
243   try:
244     _ = raw_input()
245   except KeyboardInterrupt:
246     pass
247   finally:
248     should_quit.set()
249
250   t.join()
251
252   _GenerateGraph()
253
254
255 def main(argv):
256   parser = optparse.OptionParser(usage='Usage: %prog [options]',
257                                  description=__doc__)
258   parser.add_option('-m',
259                     '--manual-graph',
260                     action='store_true',
261                     help='Manually collect data and generate a graph.')
262   parser.add_option('-p',
263                     '--package',
264                     default=constants.PACKAGE_INFO['chrome'].package,
265                     help='Package name to collect.')
266   parser.add_option('-i',
267                     '--interval',
268                     default=5,
269                     type='int',
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]))
275
276
277 if __name__ == '__main__':
278   main(sys.argv)