1#!/usr/bin/env python
2#
3# Copyright (C) 2013 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""WebView postprocessor for the go/memdump tool.
18
19Processes the output of memdump (see go/memdump) aggregating memory usage
20information for WebView analysis (both classic and chromium).
21
22Usage: adb shell /path/to/target/memdump <target-PID> | ./memreport.py > out.csv
23"""
24
25
26import os
27import re
28import sys
29
30from sets import Set
31
32
33_ENTRIES = [
34    ('Total', '.* r... .*'),
35    (' Read-only', '.* r--. .*'),
36    (' Read-write', '.* rw.. .*'),
37    ('  Read-write (no x)', '.* rw-. .*'),
38    (' Executable', '.* ..x. .*'),
39    ('Anonymous total', '.* .... .* .*shared_other=[0-9]+ ($|.*dlmalloc.*)'),
40    (' Anonymous executable (JIT)', '.* ..x. .* shared_other=[0-9]+ ($|.*dlmalloc.*)'),
41    (' Anonymous read-write', '.* rw.. .* .*shared_other=[0-9]+ ($|.*dlmalloc.*)'),
42    ('  Native heap (dlmalloc)', '.* r... .* /.*dlmalloc.*'),
43    ('File total', '.* .... .* /((?!dev/ashmem/dlmalloc).*)'),
44    (' File executable', '.* ..x. .* /((?!dev/ashmem/dlmalloc).*)'),
45    (' File read-write', '.* rw.. .* /((?!dev/ashmem/dlmalloc).*)'),
46    (' Dalvik', '.* rw.. .* /.*dalvik.*'),
47    ('  Dalvik heap', '.* rw.. .* /.*dalvik-heap.*'),
48    (' Ashmem', '.* rw.. .* /dev/ashmem .*'),
49    (' libwebcore.so total', '.* r... .* /.*libwebcore.so'),
50    ('  libwebcore.so read-only', '.* r--. .* /.*libwebcore.so'),
51    ('  libwebcore.so read-write', '.* rw-. .* /.*libwebcore.so'),
52    ('  libwebcore.so executable', '.* r.x. .* /.*libwebcore.so'),
53    (' libwebviewchromium.so total', '.* r... .* /.*libwebviewchromium.so'),
54    ('  libwebviewchromium.so read-only', '.* r--. .* /.*libwebviewchromium.so'),
55    ('  libwebviewchromium.so read-write', '.* rw-. .* /.*libwebviewchromium.so'),
56    ('  libwebviewchromium.so executable', '.* r.x. .* /.*libwebviewchromium.so'),
57    (' Driver mappings', '.* .... .* /dev/\w+$'),
58    ('  /dev/maliN total', '.* .... .* /dev/mali.*'),
59    ('OTHER (non file non anon)', '.* .... .*shared_other=[0-9]+ [^/]+'),
60    (' DMA buffers', '.* .... .*shared_other=[0-9]+ .*dmabuf.*'),
61    ]
62
63
64def _CollectMemoryStats(memdump, region_filters):
65  processes = []
66  mem_usage_for_regions = None
67  regexps = {}
68  for region_filter in region_filters:
69    regexps[region_filter] = re.compile(region_filter)
70  for line in memdump:
71    if 'PID=' in line:
72      mem_usage_for_regions = {}
73      processes.append(mem_usage_for_regions)
74      continue
75    matched_regions = Set([])
76    for region_filter in region_filters:
77      if regexps[region_filter].match(line.rstrip('\r\n')):
78        matched_regions.add(region_filter)
79        if not region_filter in mem_usage_for_regions:
80          mem_usage_for_regions[region_filter] = {
81              'private_unevictable': 0,
82              'private': 0,
83              'shared_app': 0.0,
84              'shared_other_unevictable': 0,
85              'shared_other': 0,
86          }
87    for matched_region in matched_regions:
88      mem_usage = mem_usage_for_regions[matched_region]
89      for key in mem_usage:
90        for token in line.split(' '):
91          if (key+'=') in token:
92            field = token.split('=')[1]
93            if key != 'shared_app':
94              mem_usage[key] += int(field)
95            else:  # shared_app=[\d,\d...]
96              array = eval(field)
97              for i in xrange(len(array)):
98                mem_usage[key] += float(array[i]) / (i + 2)
99            break
100  return processes
101
102
103def _ConvertMemoryField(field):
104  return str(field / (1024))
105
106
107def _DumpCSV(processes_stats):
108  total_map = {}
109  i = 0
110  for process in processes_stats:
111    i += 1
112    print ',private,private_unevictable,shared_other,shared_other_unevictable,'
113    for (k, v) in _ENTRIES:
114      header_column = k + ',' if 'NOHEADER' not in os.environ else ','
115      if not v in process:
116        print header_column + '0,0,0,0,'
117        continue
118      if not v in total_map:
119        total_map[v] = 0
120      total_map[v] += process[v]['private'] + process[v]['shared_app']
121      print (
122          header_column +
123          _ConvertMemoryField(process[v]['private']) + ',' +
124          _ConvertMemoryField(process[v]['private_unevictable']) + ',' +
125          _ConvertMemoryField(process[v]['shared_other']) + ',' +
126          _ConvertMemoryField(process[v]['shared_other_unevictable']) + ','
127          )
128
129
130def main(argv):
131  _DumpCSV(_CollectMemoryStats(sys.stdin, [value for (key, value) in _ENTRIES]))
132
133
134if __name__ == '__main__':
135  main(sys.argv)
136