1 # Copyright 2013 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.
11 LOGGER
= logging
.getLogger('dmprof')
13 BASE_PATH
= os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(__file__
)))
14 POLICIES_JSON_PATH
= os
.path
.join(BASE_PATH
, 'policies.json')
16 # Heap Profile Policy versions
18 # POLICY_DEEP_1 DOES NOT include allocation_type columns.
19 # mmap regions are distincted w/ mmap frames in the pattern column.
20 POLICY_DEEP_1
= 'POLICY_DEEP_1'
22 # POLICY_DEEP_2 DOES include allocation_type columns.
23 # mmap regions are distincted w/ the allocation_type column.
24 POLICY_DEEP_2
= 'POLICY_DEEP_2'
26 # POLICY_DEEP_3 is in JSON format.
27 POLICY_DEEP_3
= 'POLICY_DEEP_3'
29 # POLICY_DEEP_3 contains typeinfo.
30 POLICY_DEEP_4
= 'POLICY_DEEP_4'
34 """Represents one matching rule in a policy file."""
39 stackfunction_pattern
=None,
40 stacksourcefile_pattern
=None,
41 typeinfo_pattern
=None,
42 mappedpathname_pattern
=None,
43 mappedpermission_pattern
=None,
46 self
._allocator
_type
= allocator_type
48 self
._stackfunction
_pattern
= None
49 if stackfunction_pattern
:
50 self
._stackfunction
_pattern
= re
.compile(
51 stackfunction_pattern
+ r
'\Z')
53 self
._stacksourcefile
_pattern
= None
54 if stacksourcefile_pattern
:
55 self
._stacksourcefile
_pattern
= re
.compile(
56 stacksourcefile_pattern
+ r
'\Z')
58 self
._typeinfo
_pattern
= None
60 self
._typeinfo
_pattern
= re
.compile(typeinfo_pattern
+ r
'\Z')
62 self
._mappedpathname
_pattern
= None
63 if mappedpathname_pattern
:
64 self
._mappedpathname
_pattern
= re
.compile(mappedpathname_pattern
+ r
'\Z')
66 self
._mappedpermission
_pattern
= None
67 if mappedpermission_pattern
:
68 self
._mappedpermission
_pattern
= re
.compile(
69 mappedpermission_pattern
+ r
'\Z')
73 self
._sharedwith
= sharedwith
80 def allocator_type(self
):
81 return self
._allocator
_type
84 def stackfunction_pattern(self
):
85 return self
._stackfunction
_pattern
88 def stacksourcefile_pattern(self
):
89 return self
._stacksourcefile
_pattern
92 def typeinfo_pattern(self
):
93 return self
._typeinfo
_pattern
96 def mappedpathname_pattern(self
):
97 return self
._mappedpathname
_pattern
100 def mappedpermission_pattern(self
):
101 return self
._mappedpermission
_pattern
104 def sharedwith(self
):
105 return self
._sharedwith
108 class Policy(object):
109 """Represents a policy, a content of a policy file."""
111 def __init__(self
, rules
, version
, components
):
113 self
._version
= version
114 self
._components
= components
125 def components(self
):
126 return self
._components
128 def find_rule(self
, component_name
):
129 """Finds a rule whose name is |component_name|. """
130 for rule
in self
._rules
:
131 if rule
.name
== component_name
:
135 def find_malloc(self
, bucket
):
136 """Finds a matching component name which a given |bucket| belongs to.
139 bucket: A Bucket object to be searched for.
142 A string representing a component name.
144 assert not bucket
or bucket
.allocator_type
== 'malloc'
148 if bucket
.component_cache
:
149 return bucket
.component_cache
151 stackfunction
= bucket
.symbolized_joined_stackfunction
152 stacksourcefile
= bucket
.symbolized_joined_stacksourcefile
153 typeinfo
= bucket
.symbolized_typeinfo
154 if typeinfo
.startswith('0x'):
155 typeinfo
= bucket
.typeinfo_name
157 for rule
in self
._rules
:
158 if (rule
.allocator_type
== 'malloc' and
159 (not rule
.stackfunction_pattern
or
160 rule
.stackfunction_pattern
.match(stackfunction
)) and
161 (not rule
.stacksourcefile_pattern
or
162 rule
.stacksourcefile_pattern
.match(stacksourcefile
)) and
163 (not rule
.typeinfo_pattern
or rule
.typeinfo_pattern
.match(typeinfo
))):
164 bucket
.component_cache
= rule
.name
169 def find_mmap(self
, region
, bucket_set
,
170 pageframe
=None, group_pfn_counts
=None):
171 """Finds a matching component which a given mmap |region| belongs to.
173 It uses |bucket_set| to match with backtraces. If |pageframe| is given,
174 it considers memory sharing among processes.
176 NOTE: Don't use Bucket's |component_cache| for mmap regions because they're
177 classified not only with bucket information (mappedpathname for example).
180 region: A tuple representing a memory region.
181 bucket_set: A BucketSet object to look up backtraces.
182 pageframe: A PageFrame object representing a pageframe maybe including
184 group_pfn_counts: A dict mapping a PFN to the number of times the
185 the pageframe is mapped by the known "group (Chrome)" processes.
188 A string representing a component name.
190 assert region
[0] == 'hooked'
191 bucket
= bucket_set
.get(region
[1]['bucket_id'])
192 assert not bucket
or bucket
.allocator_type
== 'mmap'
195 return 'no-bucket', None
197 stackfunction
= bucket
.symbolized_joined_stackfunction
198 stacksourcefile
= bucket
.symbolized_joined_stacksourcefile
199 sharedwith
= self
._categorize
_pageframe
(pageframe
, group_pfn_counts
)
201 for rule
in self
._rules
:
202 if (rule
.allocator_type
== 'mmap' and
203 (not rule
.stackfunction_pattern
or
204 rule
.stackfunction_pattern
.match(stackfunction
)) and
205 (not rule
.stacksourcefile_pattern
or
206 rule
.stacksourcefile_pattern
.match(stacksourcefile
)) and
207 (not rule
.mappedpathname_pattern
or
208 rule
.mappedpathname_pattern
.match(region
[1]['vma']['name'])) and
209 (not rule
.mappedpermission_pattern
or
210 rule
.mappedpermission_pattern
.match(
211 region
[1]['vma']['readable'] +
212 region
[1]['vma']['writable'] +
213 region
[1]['vma']['executable'] +
214 region
[1]['vma']['private'])) and
215 (not rule
.sharedwith
or
216 not pageframe
or sharedwith
in rule
.sharedwith
)):
217 return rule
.name
, bucket
221 def find_unhooked(self
, region
, pageframe
=None, group_pfn_counts
=None):
222 """Finds a matching component which a given unhooked |region| belongs to.
224 If |pageframe| is given, it considers memory sharing among processes.
227 region: A tuple representing a memory region.
228 pageframe: A PageFrame object representing a pageframe maybe including
230 group_pfn_counts: A dict mapping a PFN to the number of times the
231 the pageframe is mapped by the known "group (Chrome)" processes.
234 A string representing a component name.
236 assert region
[0] == 'unhooked'
237 sharedwith
= self
._categorize
_pageframe
(pageframe
, group_pfn_counts
)
239 for rule
in self
._rules
:
240 if (rule
.allocator_type
== 'unhooked' and
241 (not rule
.mappedpathname_pattern
or
242 rule
.mappedpathname_pattern
.match(region
[1]['vma']['name'])) and
243 (not rule
.mappedpermission_pattern
or
244 rule
.mappedpermission_pattern
.match(
245 region
[1]['vma']['readable'] +
246 region
[1]['vma']['writable'] +
247 region
[1]['vma']['executable'] +
248 region
[1]['vma']['private'])) and
249 (not rule
.sharedwith
or
250 not pageframe
or sharedwith
in rule
.sharedwith
)):
256 def load(filename
, filetype
):
257 """Loads a policy file of |filename| in a |format|.
260 filename: A filename to be loaded.
261 filetype: A string to specify a type of the file. Only 'json' is
265 A loaded Policy object.
267 with
open(os
.path
.join(BASE_PATH
, filename
)) as policy_f
:
268 return Policy
.parse(policy_f
, filetype
)
271 def parse(policy_f
, filetype
):
272 """Parses a policy file content in a |format|.
275 policy_f: An IO object to be loaded.
276 filetype: A string to specify a type of the file. Only 'json' is
280 A loaded Policy object.
282 if filetype
== 'json':
283 return Policy
._parse
_json
(policy_f
)
288 def _parse_json(policy_f
):
289 """Parses policy file in json format.
291 A policy file contains component's names and their stacktrace pattern
292 written in regular expression. Those patterns are matched against each
293 symbols of each stacktraces in the order written in the policy file
296 policy_f: A File/IO object to read.
299 A loaded policy object.
301 policy
= json
.load(policy_f
)
304 for rule
in policy
['rules']:
305 stackfunction
= rule
.get('stackfunction') or rule
.get('stacktrace')
306 stacksourcefile
= rule
.get('stacksourcefile')
309 rule
['allocator'], # allocator_type
312 rule
['typeinfo'] if 'typeinfo' in rule
else None,
313 rule
.get('mappedpathname'),
314 rule
.get('mappedpermission'),
315 rule
.get('sharedwith')))
317 return Policy(rules
, policy
['version'], policy
['components'])
320 def _categorize_pageframe(pageframe
, group_pfn_counts
):
321 """Categorizes a pageframe based on its sharing status.
324 'private' if |pageframe| is not shared with other processes. 'group'
325 if |pageframe| is shared only with group (Chrome-related) processes.
326 'others' if |pageframe| is shared with non-group processes.
331 if pageframe
.pagecount
:
332 if pageframe
.pagecount
== 1:
334 elif pageframe
.pagecount
<= group_pfn_counts
.get(pageframe
.pfn
, 0) + 1:
339 if pageframe
.pfn
in group_pfn_counts
:
345 class PolicySet(object):
346 """Represents a set of policies."""
348 def __init__(self
, policy_directory
):
349 self
._policy
_directory
= policy_directory
352 def load(labels
=None):
353 """Loads a set of policies via the "default policy directory".
355 The "default policy directory" contains pairs of policies and their labels.
356 For example, a policy "policy.l0.json" is labeled "l0" in the default
357 policy directory "policies.json".
359 All policies in the directory are loaded by default. Policies can be
363 labels: An array that contains policy labels to be loaded.
368 default_policy_directory
= PolicySet
._load
_default
_policy
_directory
()
370 specified_policy_directory
= {}
372 if label
in default_policy_directory
:
373 specified_policy_directory
[label
] = default_policy_directory
[label
]
374 # TODO(dmikurube): Load an un-labeled policy file.
375 return PolicySet
._load
_policies
(specified_policy_directory
)
377 return PolicySet
._load
_policies
(default_policy_directory
)
380 return len(self
._policy
_directory
)
383 for label
in self
._policy
_directory
:
386 def __getitem__(self
, label
):
387 return self
._policy
_directory
[label
]
390 def _load_default_policy_directory():
391 with
open(POLICIES_JSON_PATH
, mode
='r') as policies_f
:
392 default_policy_directory
= json
.load(policies_f
)
393 return default_policy_directory
396 def _load_policies(directory
):
397 LOGGER
.info('Loading policy files.')
399 for label
in directory
:
400 LOGGER
.info(' %s: %s' % (label
, directory
[label
]['file']))
401 loaded
= Policy
.load(directory
[label
]['file'], directory
[label
]['format'])
403 policies
[label
] = loaded
404 return PolicySet(policies
)